diff --git a/.github/ISSUE_TEMPLATE/conformance-tests-checklist.yaml b/.github/ISSUE_TEMPLATE/conformance-tests-checklist.yaml deleted file mode 100644 index 293a0bd79..000000000 --- a/.github/ISSUE_TEMPLATE/conformance-tests-checklist.yaml +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Conformance tests checklist -description: To track that all Knative conformance tests for a Gateway API implementation are passing. -title: "Conformance test checklist for [implementation]" -body: - - id: conformance-tests - type: checkboxes - attributes: - description: Please change the title to include the name of the specific implementation. - label: This checklist tracks that all Knative conformance tests for this Gateway API implementation are passing. - options: - - label: basics/TestBasics - - label: basics/TestBasicsHTTP2 - - label: grpc/TestGRPC - - label: grpc/TestGRPCSplit - - label: headers/TestProbeHeaders - - label: headers/TestPreSplitSetHeaders - - label: headers/TestPostSplitSetHeaders - - label: hosts/TestMultipleHosts - - label: dispatch/TestPath - - label: dispatch/TestPercentage - - label: dispatch/TestPathAndPercentageSplit - - label: dispatch/TestRule - - label: retry/TestRetry - - label: timeout/TestTimeout - - label: tls/TestIngressTLS - - label: update/TestUpdate - - label: visibility/TestVisibility - - label: visibility/TestVisibilitySplit - - label: visibility/TestVisibilityPath - - label: ingressclass/TestIngressClass - - label: websocket/TestWebsocket - - label: websocket/TestWebsocketSplit diff --git a/README.md b/README.md index c03c088b5..ba0cb0889 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ This work is still in early development, which means it's _not ready for product This work is still in early development, which means it's _not ready for production_, but also that your feedback can have a big impact. You can also find the tested Ingress and unavailable features [here](docs/test-version.md). -## Tests -Note: conformance and e2e tests are a wip at the moment. Please see: +## KIngress Conformance Tests -- [EPIC - Contour tests · Issue #36 · knative-sandbox/net-gateway-api](https://github.com/knative-sandbox/net-gateway-api/issues/36) -- [EPIC - Istio tests · Issue #23 · knative-sandbox/net-gateway-api](https://github.com/knative-sandbox/net-gateway-api/issues/23) +We run our Knative Ingress Conformance tests and are tracking support by different implementations here: + +- [Contour Epic · Issue #384](https://github.com/knative-sandbox/net-gateway-api/issues/384) +- [Istio EPIC · Issue #383](https://github.com/knative-sandbox/net-gateway-api/issues/383) Versions to be installed are listed in [`hack/test-env.sh`](hack/test-env.sh). --- diff --git a/go.mod b/go.mod index bfef2097d..72ae970b1 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,8 @@ go 1.18 require ( github.com/google/go-cmp v0.5.8 - github.com/gorilla/websocket v1.4.2 github.com/hashicorp/golang-lru v0.5.4 go.uber.org/zap v1.19.1 - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 - google.golang.org/grpc v1.47.0 k8s.io/api v0.25.4 k8s.io/apimachinery v0.25.4 k8s.io/client-go v0.25.4 @@ -48,6 +45,7 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -76,6 +74,7 @@ require ( golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect @@ -85,6 +84,7 @@ require ( google.golang.org/api v0.61.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/test/conformance/gateway-api/basic.go b/test/conformance/gateway-api/basic.go deleted file mode 100644 index 77563b2fb..000000000 --- a/test/conformance/gateway-api/basic.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright 2019 The Knative 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 ingress - -import ( - "context" - "testing" - - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestBasics verifies that a no frills HTTPRoute exposes a simple Pod/Service via the public load balancer. -func TestBasics(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}, - }) - - RuntimeRequest(ctx, t, client, "http://"+name+".example.com") -} - -// TestBasicsHTTP2 verifies that the same no-frills HTTPRoute over a Service with http/2 configured -// will see a ProtoMajor of 2. -func TestBasicsHTTP2(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameH2C) - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}, - }) - - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com") - if ri == nil { - return - } - - if want, got := 2, ri.Request.ProtoMajor; want != got { - t.Errorf("ProtoMajor = %d, wanted %d", got, want) - } -} diff --git a/test/conformance/gateway-api/grpc.go b/test/conformance/gateway-api/grpc.go deleted file mode 100644 index bae802258..000000000 --- a/test/conformance/gateway-api/grpc.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -Copyright 2019 The Knative 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 ingress - -import ( - "context" - "fmt" - "math/rand" - "net" - "net/http" - "strings" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/pointer" - "knative.dev/net-gateway-api/test" - ping "knative.dev/networking/test/test_images/grpc-ping/proto" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestGRPC verifies that GRPC may be used via a simple Ingress. -func TestGRPC(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - const suffix = "- pong" - name, port, _ := CreateGRPCService(ctx, t, clients, suffix) - - domain := name + ".example.com" - - // Create a simple Ingress over the Service. - _, dialCtx, _ := createHTTPRouteReadyDialContext(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(domain)}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}, - }) - - // TODO: https://github.com/knative-sandbox/net-gateway-api/issues/18 - // As Ingress v2 does not have prober, it needs to make sure backend is ready. - client := &http.Client{Transport: &uaRoundTripper{RoundTripper: &http.Transport{DialContext: dialCtx}}} - waitForBackend(t, client, "http://"+name+".example.com") - - conn, err := grpc.Dial( - domain+":80", - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { - return dialCtx(ctx, "unused", addr) - }), - ) - if err != nil { - t.Fatal("Dial() =", err) - } - defer conn.Close() - pc := ping.NewPingServiceClient(conn) - - ctx, cancel := context.WithTimeout(ctx, 60*time.Second) - defer cancel() - - stream, err := pc.PingStream(ctx) - if err != nil { - t.Fatal("PingStream() =", err) - } - - for i := 0; i < 100; i++ { - checkGRPCRoundTrip(t, stream, suffix) - } -} - -// TestGRPCSplit verifies that websockets may be used across a traffic split. -func TestGRPCSplit(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - const suffixBlue = "- blue" - blueName, bluePort, _ := CreateGRPCService(ctx, t, clients, suffixBlue) - - const suffixGreen = "- green" - greenName, greenPort, _ := CreateGRPCService(ctx, t, clients, suffixGreen) - - // The suffixes we expect to see. - want := sets.NewString(suffixBlue, suffixGreen) - - // Create a simple Ingress over the Service. - name := test.ObjectNameForTest(t) - domain := name + ".example.com" - _, dialCtx, _ := createHTTPRouteReadyDialContext(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{ - { - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(bluePort), - Name: gatewayapi.ObjectName(blueName), - }, - Weight: pointer.Int32(1), - }, - }, - { - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(greenPort), - Name: gatewayapi.ObjectName(greenName), - }, - Weight: pointer.Int32(1), - }, - }, - }, - }}, - }) - - // TODO: https://github.com/knative-sandbox/net-gateway-api/issues/18 - // As Ingress v2 does not have prober, it needs to make sure backend is ready. - client := &http.Client{Transport: &uaRoundTripper{RoundTripper: &http.Transport{DialContext: dialCtx}}} - waitForBackend(t, client, "http://"+name+".example.com") - - conn, err := grpc.Dial( - domain+":80", - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { - return dialCtx(ctx, "unused", addr) - }), - ) - if err != nil { - t.Fatal("Dial() =", err) - } - defer conn.Close() - pc := ping.NewPingServiceClient(conn) - - ctx, cancel := context.WithTimeout(ctx, 60*time.Second) - defer cancel() - - const maxRequests = 100 - got := sets.NewString() - for i := 0; i < maxRequests; i++ { - stream, err := pc.PingStream(ctx) - if err != nil { - t.Error("PingStream() =", err) - continue - } - - suffix := findGRPCSuffix(t, stream) - if suffix == "" { - continue - } - got.Insert(suffix) - - for j := 0; j < 10; j++ { - checkGRPCRoundTrip(t, stream, suffix) - } - - if want.Equal(got) { - // Short circuit if we've seen all splits. - return - } - } - - // Us getting here means we haven't seen splits. - t.Errorf("(over %d requests) (-want, +got) = %s", maxRequests, cmp.Diff(want, got)) -} - -func findGRPCSuffix(t *testing.T, stream ping.PingService_PingStreamClient) string { - // Establish the suffix that corresponds to this stream. - message := fmt.Sprint("ping -", rand.Intn(1000)) - if err := stream.Send(&ping.Request{Msg: message}); err != nil { - t.Error("Error sending request:", err) - return "" - } - - resp, err := stream.Recv() - if err != nil { - t.Error("Error receiving response:", err) - return "" - } - gotMsg := resp.Msg - if !strings.HasPrefix(gotMsg, message) { - t.Errorf("Recv() = %s, wanted %s prefix", gotMsg, message) - return "" - } - return strings.TrimSpace(strings.TrimPrefix(gotMsg, message)) -} - -func checkGRPCRoundTrip(t *testing.T, stream ping.PingService_PingStreamClient, suffix string) { - message := fmt.Sprint("ping -", rand.Intn(1000)) - if err := stream.Send(&ping.Request{Msg: message}); err != nil { - t.Error("Error sending request:", err) - return - } - - // Read back the echoed message and compared with sent. - if resp, err := stream.Recv(); err != nil { - t.Error("Error receiving response:", err) - } else if got, want := resp.Msg, message+suffix; got != want { - t.Errorf("Recv() = %s, wanted %s", got, want) - } -} diff --git a/test/conformance/gateway-api/headers.go b/test/conformance/gateway-api/headers.go deleted file mode 100644 index e8903f726..000000000 --- a/test/conformance/gateway-api/headers.go +++ /dev/null @@ -1,317 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "net/http" - "testing" - - "github.com/google/go-cmp/cmp" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/pointer" - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - "knative.dev/networking/pkg/http/header" - "knative.dev/pkg/ptr" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestTagHeaders verifies that an Ingress properly dispatches to backends based on the tag header -// -// See proposal doc for reference: -// https://docs.google.com/document/d/12t_3NE4EqvW_l0hfVlQcAGKkwkAM56tTn2wN_JtHbSQ/edit?usp=sharing -func TestTagHeaders(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - const ( - tagName = "the-tag" - backendHeader = "Which-Backend" - backendWithTag = "tag" - backendWithoutTag = "no-tag" - ) - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{ - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - Matches: []gatewayapi.HTTPRouteMatch{{ - Headers: []gatewayapi.HTTPHeaderMatch{{ - Type: headerMatchTypePtr(gatewayapi.HeaderMatchExact), - Name: header.RouteTagKey, - Value: tagName, - }}, - }}, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: backendHeader, - Value: backendWithTag, - }}, - }}, - }, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: backendHeader, - Value: backendWithoutTag, - }}, - }}, - }, - }, - }, - }) - - tests := []struct { - Name string - TagHeader *string - WantBackend string - }{{ - Name: "matching tag header", - TagHeader: ptr.String(tagName), - WantBackend: backendWithTag, - }, { - Name: "no tag header", - WantBackend: backendWithoutTag, - }, { - // Note: Behavior may change in Phase 2 (see Proposal doc) - Name: "empty tag header", - TagHeader: ptr.String(""), - WantBackend: backendWithoutTag, - }, { - // Note: Behavior may change in Phase 2 (see Proposal doc) - Name: "non-matching tag header", - TagHeader: ptr.String("not-" + tagName), - WantBackend: backendWithoutTag, - }} - - for _, tt := range tests { - tt := tt - t.Run(tt.Name, func(t *testing.T) { - t.Parallel() - - ros := []RequestOption{} - - if tt.TagHeader != nil { - ros = append(ros, func(r *http.Request) { - r.Header.Set(header.RouteTagKey, *tt.TagHeader) - }) - } - - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com", ros...) - if ri == nil { - t.Error("Couldn't make request") - return - } - - if got, want := ri.Request.Headers.Get(backendHeader), tt.WantBackend; got != want { - t.Errorf("Header[%q] = %q, wanted %q", backendHeader, got, want) - } - }) - } - -} - -// TestPreSplitSetHeaders verifies that an Ingress that specified AppendHeaders pre-split has the appropriate header(s) set. -func TestPreSplitSetHeaders(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - const headerName = "Foo-Bar-Baz" - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: name, - }}, - }}}, - }}, - }) - - t.Run("Check without passing header", func(t *testing.T) { - t.Parallel() - - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com") - if ri == nil { - return - } - - if got, want := ri.Request.Headers.Get(headerName), name; got != want { - t.Errorf("Headers[%q] = %q, wanted %q", headerName, got, want) - } - }) - - t.Run("Check with passing header", func(t *testing.T) { - t.Parallel() - - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com", func(req *http.Request) { - // Specify a value for the header to verify that implementations - // use set vs. append semantics. - req.Header.Set(headerName, "bogus") - }) - if ri == nil { - return - } - - if got, want := ri.Request.Headers.Get(headerName), name; got != want { - t.Errorf("Headers[%q] = %q, wanted %q", headerName, got, want) - } - }) -} - -// TestPostSplitSetHeaders verifies that an Ingress that specified AppendHeaders post-split has the appropriate header(s) set. -func TestPostSplitSetHeaders(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - const ( - headerName = "Foo-Bar-Baz" - splits = 4 - maxRequests = 100 - ) - - backendRefs := make([]gatewayapi.HTTPBackendRef, 0, splits) - - names := make(sets.String, splits) - for i := 0; i < splits; i++ { - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - backendRefs = append(backendRefs, - gatewayapi.HTTPBackendRef{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }, - Weight: pointer.Int32(100 / splits), - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: name, - }}, - }}}, - }, - ) - names.Insert(name) - } - - // Create a simple Ingress over the 10 Services. - name := test.ObjectNameForTest(t) - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: backendRefs, - }}, - }) - - t.Run("Check without passing header", func(t *testing.T) { - t.Parallel() - - // Make enough requests that the likelihood of us seeing each variation is high, - // but don't check the distribution of requests, as that isn't the point of this - // particular test. - seen := make(sets.String, len(names)) - for i := 0; i < maxRequests; i++ { - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com") - if ri == nil { - return - } - seen.Insert(ri.Request.Headers.Get(headerName)) - if seen.Equal(names) { - // Short circuit if we've seen all headers. - return - } - } - // Us getting here means we haven't seen all headers, print the diff. - t.Errorf("(over %d requests) Header[%q] (-want, +got) = %s", - maxRequests, headerName, cmp.Diff(names, seen)) - }) - - t.Run("Check with passing header", func(t *testing.T) { - t.Parallel() - - // Make enough requests that the likelihood of us seeing each variation is high, - // but don't check the distribution of requests, as that isn't the point of this - // particular test. - seen := make(sets.String, len(names)) - for i := 0; i < maxRequests; i++ { - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com", func(req *http.Request) { - // Specify a value for the header to verify that implementations - // use set vs. append semantics. - req.Header.Set(headerName, "bogus") - }) - if ri == nil { - return - } - seen.Insert(ri.Request.Headers.Get(headerName)) - if seen.Equal(names) { - // Short circuit if we've seen all headers. - return - } - } - // Us getting here means we haven't seen all headers, print the diff. - t.Errorf("(over %d requests) Header[%q] (-want, +got) = %s", - maxRequests, headerName, cmp.Diff(names, seen)) - }) -} diff --git a/test/conformance/gateway-api/hosts.go b/test/conformance/gateway-api/hosts.go deleted file mode 100644 index e8e36b627..000000000 --- a/test/conformance/gateway-api/hosts.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "testing" - - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestMultipleHosts verifies that an Ingress can respond to multiple hosts. -func TestMultipleHosts(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - hosts := []gatewayapi.Hostname{ - "foo.com", - "www.foo.com", - "a-b-1.something-really-really-long.knative.dev", - "add.your.interesting.domain.here.io", - } - - // Using fixed hostnames can lead to conflicts when -count=N>1 - // so pseudo-randomize the hostnames to avoid conflicts. - for i, host := range hosts { - hosts[i] = gatewayapi.Hostname(name + "." + string(host)) - } - - // Create a simple HTTPRoute over the Service. - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: hosts, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}, - }) - - for _, host := range hosts { - RuntimeRequest(ctx, t, client, "http://"+string(host)) - } -} diff --git a/test/conformance/gateway-api/path.go b/test/conformance/gateway-api/path.go deleted file mode 100644 index 3b14c2856..000000000 --- a/test/conformance/gateway-api/path.go +++ /dev/null @@ -1,311 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "errors" - "math" - "testing" - - "golang.org/x/sync/errgroup" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/pointer" - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestPath verifies that an Ingress properly dispatches to backends based on the path of the URL. -func TestPath(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - // For /foo - fooName, fooPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // For /bar - barName, barPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // For /baz - bazName, bazPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // Use a post-split injected header to establish which split we are sending traffic to. - const headerName = "Which-Backend" - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{ - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(fooPort), - Name: gatewayapi.ObjectName(fooName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: fooName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/foo"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(barPort), - Name: gatewayapi.ObjectName(barName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: barName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/bar"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(bazPort), - Name: gatewayapi.ObjectName(bazName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: bazName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/baz"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: name, - }}, - }}, - }, - }}, - }, - }, - }) - - tests := map[string]string{ - "/foo": fooName, - "/bar": barName, - "/baz": bazName, - "": name, - "/asdf": name, - } - - for path, want := range tests { - path, want := path, want - t.Run(path, func(t *testing.T) { - t.Parallel() - - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com"+path) - if ri == nil { - return - } - - got := ri.Request.Headers.Get(headerName) - if got != want { - t.Errorf("Header[%q] = %q, wanted %q", headerName, got, want) - } - }) - } -} - -func TestPathAndPercentageSplit(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - fooName, fooPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - barName, barPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // Use a post-split injected header to establish which split we are sending traffic to. - const headerName = "Which-Backend" - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{ - { - BackendRefs: []gatewayapi.HTTPBackendRef{ - { - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(fooPort), - Name: gatewayapi.ObjectName(fooName), - }, - Weight: pointer.Int32(1), - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: fooName, - }}, - }}, - }, - }, - { - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(barPort), - Name: gatewayapi.ObjectName(barName), - }, - Weight: pointer.Int32(1), - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: barName, - }}, - }}, - }, - }, - }, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/foo"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: name, - }}, - }}, - }, - }}, - }, - }, - }) - - const ( - total = 1000 - totalHalf = total / 2 - tolerance = total * 0.15 - ) - wantKeys := sets.NewString(fooName, barName) - resultCh := make(chan string, total) - - var g errgroup.Group - g.SetLimit(8) - - for i := 0; i < total; i++ { - g.Go(func() error { - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com/foo") - if ri == nil { - return errors.New("failed to request") - } - resultCh <- ri.Request.Headers.Get(headerName) - return nil - }) - } - if err := g.Wait(); err != nil { - t.Error("Error while sending requests:", err) - } - close(resultCh) - - got := make(map[string]float64, len(wantKeys)) - for r := range resultCh { - got[r]++ - } - for k, v := range got { - if !wantKeys.Has(k) { - t.Errorf("%s is not in the expected header say %v", k, wantKeys) - } - if math.Abs(v-totalHalf) > tolerance { - t.Errorf("Header %s got: %v times, want in [%v, %v] range", k, v, totalHalf-tolerance, totalHalf+tolerance) - } - } -} diff --git a/test/conformance/gateway-api/percentage.go b/test/conformance/gateway-api/percentage.go deleted file mode 100644 index 266e2e18b..000000000 --- a/test/conformance/gateway-api/percentage.go +++ /dev/null @@ -1,139 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "errors" - "math" - "testing" - - "golang.org/x/sync/errgroup" - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestPercentage verifies that an Ingress splitting over multiple backends respects -// the given percentage distribution. -func TestPercentage(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - // Use a post-split injected header to establish which split we are sending traffic to. - const headerName = "Foo-Bar-Baz" - - backends := make([]gatewayapi.HTTPBackendRef, 0, 10) - weights := make(map[string]float64, len(backends)) - - // Double the percentage of the split each iteration until it would overflow, and then - // give the last route the remainder. - percent, total := int32(1), int32(0) - for i := 0; i < 10; i++ { - weight := percent - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - backends = append(backends, - gatewayapi.HTTPBackendRef{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }, - Weight: &weight, - }, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: name, - }}, - }}, - }}, - ) - weights[name] = float64(percent) - - total += percent - percent *= 2 - // Cap the final non-zero bucket so that we total 100% - // After that, this will zero out remaining buckets. - if total+percent > 100 { - percent = 100 - total - } - } - - // Create a simple HTTPRoute over the 10 Services. - name := test.ObjectNameForTest(t) - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: backends, - }}, - }) - - // Create a large enough population of requests that we can reasonably assess how - // well the Ingress respected the percentage split. - seen := make(map[string]float64, len(backends)) - - const ( - // The total number of requests to make (as a float to avoid conversions in later computations). - totalRequests = 1000.0 - // The increment to make for each request, so that the values of seen reflect the - // percentage of the total number of requests we are making. - increment = 100.0 / totalRequests - // Allow the Ingress to be within 10% of the configured value. - margin = 10.0 - ) - var g errgroup.Group - g.SetLimit(8) - resultCh := make(chan string, totalRequests) - - for i := 0.0; i < totalRequests; i++ { - g.Go(func() error { - ri := RuntimeRequest(ctx, t, client, "http://"+name+".example.com") - if ri == nil { - return errors.New("failed to request") - } - resultCh <- ri.Request.Headers.Get(headerName) - return nil - }) - } - if err := g.Wait(); err != nil { - t.Error("Error while sending requests:", err) - } - close(resultCh) - - for r := range resultCh { - seen[r] += increment - } - - for name, want := range weights { - got := seen[name] - switch { - case want == 0.0 && got > 0.0: - // For 0% targets, we have tighter requirements. - t.Errorf("Target %q received traffic, wanted none (0%% target).", name) - case math.Abs(got-want) > margin: - t.Errorf("Target %q received %f%%, wanted %f +/- %f", name, got, want, margin) - } - } -} diff --git a/test/conformance/gateway-api/rule.go b/test/conformance/gateway-api/rule.go deleted file mode 100644 index 2bc5c2875..000000000 --- a/test/conformance/gateway-api/rule.go +++ /dev/null @@ -1,120 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "testing" - - "k8s.io/utils/pointer" - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestRule verifies that an Ingress properly dispatches to backends based on different rules. -func TestRule(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - // Use a pre-split injected header to establish which rule we are sending traffic to. - const headerName = "Foo-Bar-Baz" - - fooName, fooPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - barName, barPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(fooName + ".example.com"), gatewayapi.Hostname(barName + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{ - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(fooPort), - Name: gatewayapi.ObjectName(fooName), - }, - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: fooName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Headers: []gatewayapi.HTTPHeaderMatch{{ - Type: headerMatchTypePtr(gatewayapi.HeaderMatchExact), - Name: "Host", - Value: fooName + ".example.com", - }}, - // This should be removed once https://github.com/kubernetes-sigs/gateway-api/issues/563 was solved. - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(barPort), - Name: gatewayapi.ObjectName(barName), - }, - }, - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: barName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Headers: []gatewayapi.HTTPHeaderMatch{{ - Type: headerMatchTypePtr(gatewayapi.HeaderMatchExact), - Name: "Host", - Value: barName + ".example.com", - }}, - // This should be removed once https://github.com/kubernetes-sigs/gateway-api/issues/563 was solved. - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/"), - }, - }}, - }, - }, - }) - - ri := RuntimeRequest(ctx, t, client, "http://"+fooName+".example.com") - if got := ri.Request.Headers.Get(headerName); got != fooName { - t.Errorf("Header[Host] = %q, wanted %q", got, fooName) - } - - ri = RuntimeRequest(ctx, t, client, "http://"+barName+".example.com") - if got := ri.Request.Headers.Get(headerName); got != barName { - t.Errorf("Header[Host] = %q, wanted %q", got, barName) - } -} diff --git a/test/conformance/gateway-api/run.go b/test/conformance/gateway-api/run.go deleted file mode 100644 index a442a4bfc..000000000 --- a/test/conformance/gateway-api/run.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -Copyright 2020 The Knative 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 ingress - -import ( - "strings" - "testing" - - "knative.dev/networking/test" -) - -var stableTests = map[string]func(t *testing.T){ - "basics": TestBasics, - "basics/http2": TestBasicsHTTP2, - "headers/pre-split": TestPreSplitSetHeaders, - "headers/post-split": TestPostSplitSetHeaders, - "dispatch/path": TestPath, - "dispatch/percentage": TestPercentage, - "dispatch/path_and_percentage": TestPathAndPercentageSplit, - "dispatch/rule": TestRule, - "hosts/multiple": TestMultipleHosts, - "timeout": TestTimeout, - "websocket": TestWebsocket, - "websocket/split": TestWebsocketSplit, - "grpc": TestGRPC, - "grpc/split": TestGRPCSplit, - "visibility": TestVisibility, - "visibility/split": TestVisibilitySplit, - "visibility/path": TestVisibilityPath, - /* TODO: not implemented yet. - "headers/probe": TestProbeHeaders, - "retry": TestRetry, - "tls": TestIngressTLS, - "update": TestUpdate, - "ingressclass": TestIngressClass, - */ -} - -var betaTests = map[string]func(t *testing.T){ - // Add your conformance test for beta features - "headers/tags": TestTagHeaders, - // Since v1alpha2, it needs different implementation. - // "host-rewrite": TestRewriteHost, -} - -var alphaTests = map[string]func(t *testing.T){ - // Add your conformance test for alpha features - // "httpoption": TestHTTPOption, -} - -// RunConformance will run ingress conformance tests -// -// Depending on the options it may test alpha and beta features -func RunConformance(t *testing.T) { - skipTests := skipTests() - - for name, test := range stableTests { - if _, ok := skipTests[name]; ok { - t.Run(name, skipFunc) - continue - } - t.Run(name, test) - } - - // TODO(dprotaso) we'll need something more robust - // in the long term that lets downstream - // implementations to better select which tests - // should be run - selection across various - // dimensions - // ie. state - alpha, beta, ga - // ie. requirement - must, should, may - if test.NetworkingFlags.EnableBetaFeatures { - for name, test := range betaTests { - if _, ok := skipTests[name]; ok { - t.Run(name, skipFunc) - continue - } - t.Run(name, test) - } - } - - if test.NetworkingFlags.EnableAlphaFeatures { - for name, test := range alphaTests { - if _, ok := skipTests[name]; ok { - t.Run(name, skipFunc) - continue - } - t.Run(name, test) - } - } -} - -var skipFunc = func(t *testing.T) { - t.Skip("Skipping the test in skip-test flag") -} - -func skipTests() map[string]struct{} { - skipArray := strings.Split(test.NetworkingFlags.SkipTests, ",") - skipMap := make(map[string]struct{}, len(skipArray)) - for _, name := range skipArray { - skipMap[name] = struct{}{} - } - return skipMap -} diff --git a/test/conformance/gateway-api/run_test.go b/test/conformance/gateway-api/run_test.go deleted file mode 100644 index b902c3dac..000000000 --- a/test/conformance/gateway-api/run_test.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build e2e -// +build e2e - -/* -Copyright 2020 The Knative 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 ingress - -import "testing" - -func TestIngressConformance(t *testing.T) { - RunConformance(t) -} diff --git a/test/conformance/gateway-api/timeout.go b/test/conformance/gateway-api/timeout.go deleted file mode 100644 index fa10b52fe..000000000 --- a/test/conformance/gateway-api/timeout.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "fmt" - "net/http" - "testing" - "time" - - "knative.dev/net-gateway-api/test" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestTimeout verifies that an Ingress implements "no timeout". -func TestTimeout(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - name, port, _ := CreateTimeoutService(ctx, t, clients) - - // Create a simple HTTPRoute over the Service. - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(name + ".example.com")}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}, - }) - - const timeout = 10 * time.Second - - tests := []struct { - name string - code int - initialDelay time.Duration - delay time.Duration - }{{ - name: "no delays is OK", - code: http.StatusOK, - }, { - name: "large delay before headers is ok", - code: http.StatusOK, - initialDelay: timeout, - }, { - name: "large delay after headers is ok", - code: http.StatusOK, - delay: timeout, - }} - - // TODO: https://github.com/knative-sandbox/net-gateway-api/issues/18 - // As Ingress v2 does not have prober, it needs to make sure backend is ready. - waitForBackend(t, client, "http://"+name+".example.com?initialTimeout=0&timeout=0") - - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - checkTimeout(ctx, t, client, name, test.code, test.initialDelay, test.delay) - }) - } -} - -func checkTimeout(ctx context.Context, t *testing.T, client *http.Client, name string, code int, initial time.Duration, timeout time.Duration) { - t.Helper() - - resp, err := client.Get(fmt.Sprintf("http://%s.example.com?initialTimeout=%d&timeout=%d", - name, initial.Milliseconds(), timeout.Milliseconds())) - if err != nil { - t.Fatal("Error making GET request:", err) - } - defer resp.Body.Close() - if resp.StatusCode != code { - t.Errorf("Unexpected status code: %d, wanted %d", resp.StatusCode, code) - DumpResponse(ctx, t, resp) - } -} diff --git a/test/conformance/gateway-api/util.go b/test/conformance/gateway-api/util.go deleted file mode 100644 index 8426f5c98..000000000 --- a/test/conformance/gateway-api/util.go +++ /dev/null @@ -1,1145 +0,0 @@ -/* -Copyright 2019 The Knative 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 ingress - -import ( - "bytes" - "context" - "crypto/ecdsa" - "crypto/elliptic" - cryptorand "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/json" - "encoding/pem" - "errors" - "fmt" - "io" - "math/big" - "math/rand" - "net" - "net/http" - "net/http/httputil" - "os" - "strconv" - "strings" - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - apierrs "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ktypes "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - "knative.dev/networking/test/types" - "knative.dev/pkg/network" - "knative.dev/pkg/reconciler" - pkgTest "knative.dev/pkg/test" - "knative.dev/pkg/test/logging" - - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func pathMatchTypePtr(val gatewayapi.PathMatchType) *gatewayapi.PathMatchType { - return &val -} - -func headerMatchTypePtr(val gatewayapi.HeaderMatchType) *gatewayapi.HeaderMatchType { - return &val -} - -func portNumPtr(port int) *gatewayapi.PortNumber { - pn := gatewayapi.PortNumber(port) - return &pn -} - -func namespacePtr(val gatewayapi.Namespace) *gatewayapi.Namespace { - return &val -} - -var dialBackoff = wait.Backoff{ - Duration: 50 * time.Millisecond, - Factor: 1.4, - Jitter: 0.1, // At most 10% jitter. - Steps: 100, - Cap: 10 * time.Second, -} - -var testGateway = gatewayapi.ParentReference{ - Name: "knative-gateway", - Namespace: namespacePtr(gatewayNamespace()), -} - -var testLocalGateway = gatewayapi.ParentReference{ - Name: "knative-local-gateway", - Namespace: namespacePtr(localGatewayNamespace()), -} - -func gatewayNamespace() gatewayapi.Namespace { - namespace := "istio-system" - if gatewayNsOverride := os.Getenv("GATEWAY_NAMESPACE_OVERRIDE"); gatewayNsOverride != "" { - namespace = gatewayNsOverride - } - return gatewayapi.Namespace(namespace) -} - -func localGatewayNamespace() gatewayapi.Namespace { - namespace := "istio-system" - if gatewayNsOverride := os.Getenv("LOCAL_GATEWAY_NAMESPACE_OVERRIDE"); gatewayNsOverride != "" { - namespace = gatewayNsOverride - } - return gatewayapi.Namespace(namespace) -} - -// uaRoundTripper wraps the given http.RoundTripper and -// sets a custom UserAgent. -type uaRoundTripper struct { - http.RoundTripper - ua string -} - -// RoundTrip implements http.RoundTripper. -func (ua *uaRoundTripper) RoundTrip(rq *http.Request) (*http.Response, error) { - rq.Header.Set("User-Agent", ua.ua) - return ua.RoundTripper.RoundTrip(rq) -} - -// CreateRuntimeService creates a Kubernetes service that will respond to the protocol -// specified with the given portName. It returns the service name, the port on -// which the service is listening, and a "cancel" function to clean up the -// created resources. -func CreateRuntimeService(ctx context.Context, t *testing.T, clients *test.Clients, portName string) (string, int, context.CancelFunc) { - t.Helper() - name := test.ObjectNameForTest(t) - - // Avoid zero, but pick a low port number. - port := 50 + rand.Intn(50) - t.Logf("[%s] Using port %d", name, port) - - // Pick a high port number. - containerPort := 8000 + rand.Intn(100) - t.Logf("[%s] Using containerPort %d", name, containerPort) - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "foo", - Image: pkgTest.ImagePath("runtime"), - ImagePullPolicy: corev1.PullIfNotPresent, - Ports: []corev1.ContainerPort{{ - Name: portName, - ContainerPort: int32(containerPort), - }}, - // This is needed by the runtime image we are using. - Env: []corev1.EnvVar{{ - Name: "PORT", - Value: strconv.Itoa(containerPort), - }}, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/healthz", - Port: intstr.FromInt(containerPort), - }, - }, - }, - }}, - }, - } - - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.ServiceSpec{ - Type: "ClusterIP", - Ports: []corev1.ServicePort{{ - Name: portName, - Port: int32(port), - TargetPort: intstr.FromInt(containerPort), - }}, - Selector: map[string]string{ - "test-pod": name, - }, - }, - } - - return name, port, createPodAndService(ctx, t, clients, pod, svc) -} - -// CreateProxyService creates a Kubernetes service that will forward requests to -// the specified target. It returns the service name, the port on which the service -// is listening, and a "cancel" function to clean up the created resources. -func CreateProxyService(ctx context.Context, t *testing.T, clients *test.Clients, target string, gatewayDomain string) (string, int, context.CancelFunc) { - t.Helper() - name := test.ObjectNameForTest(t) - - // Avoid zero, but pick a low port number. - port := 50 + rand.Intn(50) - t.Logf("[%s] Using port %d", name, port) - - // Pick a high port number. - containerPort := 8000 + rand.Intn(100) - t.Logf("[%s] Using containerPort %d", name, containerPort) - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "foo", - Image: pkgTest.ImagePath("httpproxy"), - ImagePullPolicy: corev1.PullIfNotPresent, - Ports: []corev1.ContainerPort{{ - ContainerPort: int32(containerPort), - }}, - Env: []corev1.EnvVar{{ - Name: "TARGET_HOST", - Value: target, - }, { - Name: "PORT", - Value: strconv.Itoa(containerPort), - }}, - }}, - }, - } - - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.ServiceSpec{ - Type: "ClusterIP", - Ports: []corev1.ServicePort{{ - Port: int32(port), - TargetPort: intstr.FromInt(containerPort), - }}, - Selector: map[string]string{ - "test-pod": name, - }, - }, - } - proxyServiceCancel := createPodAndService(ctx, t, clients, pod, svc) - - externalNameServiceCancel := createExternalNameService(ctx, t, clients, target, gatewayDomain) - - return name, port, func() { - externalNameServiceCancel() - proxyServiceCancel() - } -} - -// CreateTimeoutService creates a Kubernetes service that will respond to the protocol -// specified with the given portName. It returns the service name, the port on -// which the service is listening, and a "cancel" function to clean up the -// created resources. -func CreateTimeoutService(ctx context.Context, t *testing.T, clients *test.Clients) (string, int, context.CancelFunc) { - t.Helper() - name := test.ObjectNameForTest(t) - - // Avoid zero, but pick a low port number. - port := 50 + rand.Intn(50) - t.Logf("[%s] Using port %d", name, port) - - // Pick a high port number. - containerPort := 8000 + rand.Intn(100) - t.Logf("[%s] Using containerPort %d", name, containerPort) - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "foo", - Image: pkgTest.ImagePath("timeout"), - ImagePullPolicy: corev1.PullIfNotPresent, - Ports: []corev1.ContainerPort{{ - Name: networking.ServicePortNameHTTP1, - ContainerPort: int32(containerPort), - }}, - // This is needed by the timeout image we are using. - Env: []corev1.EnvVar{{ - Name: "PORT", - Value: strconv.Itoa(containerPort), - }}, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(containerPort), - }, - }, - }, - }}, - }, - } - - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.ServiceSpec{ - Type: "ClusterIP", - Ports: []corev1.ServicePort{{ - Name: networking.ServicePortNameHTTP1, - Port: int32(port), - TargetPort: intstr.FromInt(containerPort), - }}, - Selector: map[string]string{ - "test-pod": name, - }, - }, - } - - return name, port, createPodAndService(ctx, t, clients, pod, svc) -} - -// CreateWebsocketService creates a Kubernetes service that will upgrade the connection -// to use websockets and echo back the received messages with the provided suffix. -func CreateWebsocketService(ctx context.Context, t *testing.T, clients *test.Clients, suffix string) (string, int, context.CancelFunc) { - t.Helper() - name := test.ObjectNameForTest(t) - - // Avoid zero, but pick a low port number. - port := 50 + rand.Intn(50) - t.Logf("[%s] Using port %d", name, port) - - // Pick a high port number. - containerPort := 8000 + rand.Intn(100) - t.Logf("[%s] Using containerPort %d", name, containerPort) - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "foo", - Image: pkgTest.ImagePath("wsserver"), - ImagePullPolicy: corev1.PullIfNotPresent, - Ports: []corev1.ContainerPort{{ - Name: networking.ServicePortNameHTTP1, - ContainerPort: int32(containerPort), - }}, - // This is needed by the runtime image we are using. - Env: []corev1.EnvVar{{ - Name: "PORT", - Value: strconv.Itoa(containerPort), - }, { - Name: "SUFFIX", - Value: suffix, - }}, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/", - Port: intstr.FromInt(containerPort), - }, - }, - }, - }}, - }, - } - - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.ServiceSpec{ - Type: "ClusterIP", - Ports: []corev1.ServicePort{{ - Name: networking.ServicePortNameHTTP1, - Port: int32(port), - TargetPort: intstr.FromInt(containerPort), - }}, - Selector: map[string]string{ - "test-pod": name, - }, - }, - } - - return name, port, createPodAndService(ctx, t, clients, pod, svc) -} - -// CreateGRPCService creates a Kubernetes service that will upgrade the connection -// to use GRPC and echo back the received messages with the provided suffix. -func CreateGRPCService(ctx context.Context, t *testing.T, clients *test.Clients, suffix string) (string, int, context.CancelFunc) { - t.Helper() - name := test.ObjectNameForTest(t) - - // Avoid zero, but pick a low port number. - port := 50 + rand.Intn(50) - t.Logf("[%s] Using port %d", name, port) - - // Pick a high port number. - containerPort := 8000 + rand.Intn(100) - t.Logf("[%s] Using containerPort %d", name, containerPort) - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "foo", - Image: pkgTest.ImagePath("grpc-ping"), - ImagePullPolicy: corev1.PullIfNotPresent, - Ports: []corev1.ContainerPort{{ - Name: networking.ServicePortNameH2C, - ContainerPort: int32(containerPort), - }}, - // This is needed by the runtime image we are using. - Env: []corev1.EnvVar{{ - Name: "PORT", - Value: strconv.Itoa(containerPort), - }, { - Name: "SUFFIX", - Value: suffix, - }}, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(containerPort), - }, - }, - }, - }}, - }, - } - - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.ServiceSpec{ - Type: "ClusterIP", - Ports: []corev1.ServicePort{{ - Name: networking.ServicePortNameH2C, - Port: int32(port), - TargetPort: intstr.FromInt(containerPort), - }}, - Selector: map[string]string{ - "test-pod": name, - }, - }, - } - - return name, port, createPodAndService(ctx, t, clients, pod, svc) -} - -// CreateRetryService creates a service that will return a 503 on first access, and then 200 after that. -func CreateRetryService(ctx context.Context, t *testing.T, clients *test.Clients) (string, int, context.CancelFunc) { - t.Helper() - name := test.ObjectNameForTest(t) - - // Avoid zero, but pick a low port number. - port := 50 + rand.Intn(50) - t.Logf("[%s] Using port %d", name, port) - - // Pick a high port number. - containerPort := 8000 + rand.Intn(100) - t.Logf("[%s] Using containerPort %d", name, containerPort) - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Name: "foo", - Image: pkgTest.ImagePath("retry"), - ImagePullPolicy: corev1.PullIfNotPresent, - Ports: []corev1.ContainerPort{{ - Name: networking.ServicePortNameH2C, - ContainerPort: int32(containerPort), - }}, - // This is needed by the runtime image we are using. - Env: []corev1.EnvVar{{ - Name: "PORT", - Value: strconv.Itoa(containerPort), - }}, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(containerPort), - }, - }, - }, - }}, - }, - } - - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-pod": name, - }, - }, - Spec: corev1.ServiceSpec{ - Type: "ClusterIP", - Ports: []corev1.ServicePort{{ - Name: networking.ServicePortNameH2C, - Port: int32(port), - TargetPort: intstr.FromInt(containerPort), - }}, - Selector: map[string]string{ - "test-pod": name, - }, - }, - } - - return name, port, createPodAndService(ctx, t, clients, pod, svc) -} - -// createService is a helper for creating the service resource. -func createService(ctx context.Context, t *testing.T, clients *test.Clients, svc *corev1.Service) context.CancelFunc { - t.Helper() - - svcName := ktypes.NamespacedName{Name: svc.Name, Namespace: svc.Namespace} - - t.Cleanup(func() { - clients.KubeClient.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) - }) - if err := reconciler.RetryTestErrors(func(attempts int) error { - if attempts > 0 { - t.Logf("Attempt %d creating service %s", attempts, svc.Name) - } - _, err := clients.KubeClient.CoreV1().Services(svc.Namespace).Create(ctx, svc, metav1.CreateOptions{}) - if err != nil { - t.Logf("Attempt %d creating service failed with: %v", attempts, err) - } - return err - }); err != nil { - t.Fatalf("Error creating Service %q: %v", svcName, err) - } - - return func() { - err := clients.KubeClient.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Error cleaning up Service %q: %v", svcName, err) - } - } -} - -func createExternalNameService(ctx context.Context, t *testing.T, clients *test.Clients, target, gatewayDomain string) context.CancelFunc { - t.Helper() - - targetName := strings.SplitN(target, ".", 3) - externalNameSvc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: targetName[0], - Namespace: targetName[1], - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeExternalName, - ExternalName: gatewayDomain, - SessionAffinity: corev1.ServiceAffinityNone, - Ports: []corev1.ServicePort{{ - Name: networking.ServicePortNameH2C, - Port: int32(80), - TargetPort: intstr.FromInt(80), - }}, - }, - } - - return createService(ctx, t, clients, externalNameSvc) -} - -// createPodAndService is a helper for creating the pod and service resources, setting -// up their context.CancelFunc, and waiting for it to become ready. -func createPodAndService(ctx context.Context, t *testing.T, clients *test.Clients, pod *corev1.Pod, svc *corev1.Service) context.CancelFunc { - t.Helper() - - podName := ktypes.NamespacedName{Name: pod.Name, Namespace: pod.Namespace} - svcName := ktypes.NamespacedName{Name: svc.Name, Namespace: svc.Namespace} - - t.Cleanup(func() { - clients.KubeClient.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) - }) - if err := reconciler.RetryTestErrors(func(attempts int) error { - if attempts > 0 { - t.Logf("Attempt %d creating pod %s", attempts, pod.Name) - } - _, err := clients.KubeClient.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) - if err != nil { - t.Logf("Attempt %d creating pod failed with: %v", attempts, err) - } - return err - }); err != nil { - t.Fatalf("Error creating Pod %q: %v", podName, err) - } - - t.Cleanup(func() { - clients.KubeClient.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) - }) - if err := reconciler.RetryTestErrors(func(attempts int) error { - if attempts > 0 { - t.Logf("Attempt %d creating service %s", attempts, svc.Name) - } - _, err := clients.KubeClient.CoreV1().Services(svc.Namespace).Create(ctx, svc, metav1.CreateOptions{}) - if err != nil { - t.Logf("Attempt %d creating service failed with: %v", attempts, err) - } - return err - }); err != nil { - t.Fatalf("Error creating Service %q: %v", svcName, err) - } - - // Wait for the Pod to show up in the Endpoints resource. - waitErr := wait.PollImmediate(test.PollInterval, test.PollTimeout, func() (bool, error) { - var ep *corev1.Endpoints - err := reconciler.RetryTestErrors(func(attempts int) (err error) { - ep, err = clients.KubeClient.CoreV1().Endpoints(svc.Namespace).Get(ctx, svc.Name, metav1.GetOptions{}) - return err - }) - if apierrs.IsNotFound(err) { - return false, nil - } else if err != nil { - return true, err - } - - for _, subset := range ep.Subsets { - if len(subset.Addresses) == 0 { - return false, nil - } - } - return len(ep.Subsets) > 0, nil - }) - if waitErr != nil { - t.Fatalf("Error waiting for %q Endpoints to contain a Pod IP: %v", svcName, waitErr) - } - - return func() { - err := clients.KubeClient.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Error cleaning up Service %q: %v", svcName, err) - } - err = clients.KubeClient.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Error cleaning up Pod %q", pod.Name) - } - } -} - -// Option enables further configuration of a HTTPRoute. -type Option func(*gatewayapi.HTTPRoute) - -// OverrideHTTPRouteAnnotation overrides the HTTPRoute annotation. -func OverrideHTTPRouteAnnotation(annotations map[string]string) Option { - return func(hr *gatewayapi.HTTPRoute) { - hr.Annotations = annotations - } -} - -func createHTTPRouteReadyDialContext(ctx context.Context, t *testing.T, clients *test.Clients, spec gatewayapi.HTTPRouteSpec, io ...Option) (*gatewayapi.HTTPRoute, func(context.Context, string, string) (net.Conn, error), context.CancelFunc) { - t.Helper() - - hr, cancel := CreateHTTPRoute(ctx, t, clients, spec, io...) - hrName := ktypes.NamespacedName{Name: hr.Name, Namespace: hr.Namespace} - - if err := WaitForHTTPRouteState(ctx, clients.GatewayAPIClient, hr.Name, IsHTTPRouteReady, t.Name()); err != nil { - cancel() - t.Fatalf("Error waiting for HTTPRoute %q state: %v", hrName, err) - } - err := reconciler.RetryTestErrors(func(attempts int) (err error) { - hr, err = clients.GatewayAPIClient.HTTPRoutes.Get(ctx, hr.Name, metav1.GetOptions{}) - return err - }) - if err != nil { - cancel() - t.Fatalf("Error getting HTTPRoute %q: %v", hrName, err) - } - - // Create a dialer based. - return hr, CreateDialContext(ctx, t, clients), cancel -} - -// CreateHTTPRoute creates a HTTPRoute resource -func CreateHTTPRoute(ctx context.Context, t *testing.T, clients *test.Clients, spec gatewayapi.HTTPRouteSpec, io ...Option) (*gatewayapi.HTTPRoute, context.CancelFunc) { - t.Helper() - - name := test.ObjectNameForTest(t) - - // Create a simple HTTPRoute over the Service. - hr := &gatewayapi.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - }, - Spec: spec, - } - - for _, opt := range io { - opt(hr) - } - - hrName := ktypes.NamespacedName{Name: hr.Name, Namespace: hr.Namespace} - - t.Cleanup(func() { clients.GatewayAPIClient.HTTPRoutes.Delete(ctx, hr.Name, metav1.DeleteOptions{}) }) - if err := reconciler.RetryTestErrors(func(attempts int) (err error) { - if attempts > 0 { - t.Logf("Attempt %d creating httproute %s", attempts, hr.Name) - } - hr, err = clients.GatewayAPIClient.HTTPRoutes.Create(ctx, hr, metav1.CreateOptions{}) - if err != nil { - t.Logf("Attempt %d creating httproute failed with: %v", attempts, err) - } - return err - }); err != nil { - t.Fatalf("Error creating HTTPRoute %q: %v", hrName, err) - } - - return hr, func() { - err := clients.GatewayAPIClient.HTTPRoutes.Delete(ctx, hr.Name, metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Error cleaning up HTTPRoute %q: %v", hrName, err) - } - } -} - -func CreateHTTPRouteReady(ctx context.Context, t *testing.T, clients *test.Clients, spec gatewayapi.HTTPRouteSpec, io ...Option) (*gatewayapi.HTTPRoute, *http.Client, context.CancelFunc) { - return CreateHTTPRouteReadyWithTLS(ctx, t, clients, spec, nil) -} - -func CreateHTTPRouteReadyWithTLS(ctx context.Context, t *testing.T, clients *test.Clients, spec gatewayapi.HTTPRouteSpec, tlsConfig *tls.Config, io ...Option) (*gatewayapi.HTTPRoute, *http.Client, context.CancelFunc) { - t.Helper() - - // Create a client with a dialer based on the HTTPRoute' public load balancer. - hr, dialer, cancel := createHTTPRouteReadyDialContext(ctx, t, clients, spec, io...) - - return hr, &http.Client{ - Transport: &uaRoundTripper{ - RoundTripper: &http.Transport{ - DialContext: dialer, - TLSClientConfig: tlsConfig, - }, - ua: fmt.Sprintf("knative.dev/%s/%s", t.Name(), hr.Name), - }, - }, cancel -} - -// CreateTLSSecret creates a secret with TLS certs in the serving namespace. -// This is based on https://golang.org/src/crypto/tls/generate_cert.go -func CreateTLSSecret(ctx context.Context, t *testing.T, clients *test.Clients, hosts []string) (string, *tls.Config, context.CancelFunc) { - t.Helper() - - priv, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) - if err != nil { - t.Fatal("ecdsa.GenerateKey() =", err) - } - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := cryptorand.Int(cryptorand.Reader, serialNumberLimit) - if err != nil { - t.Fatal("Failed to generate serial number:", err) - } - - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{"Knative HTTPRoute Conformance Testing"}, - }, - - // Only let it live briefly. - NotBefore: time.Now(), - NotAfter: time.Now().Add(5 * time.Minute), - - IsCA: true, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - - DNSNames: hosts, - } - - derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - t.Fatal("x509.CreateCertificate() =", err) - } - - cert, err := x509.ParseCertificate(derBytes) - if err != nil { - t.Fatal("ParseCertificate() =", err) - } - pool := x509.NewCertPool() - // Ideally we'd undo this in "cancel", but there doesn't - // seem to be a mechanism to remove things from a pool. - pool.AddCert(cert) - - certPEM := &bytes.Buffer{} - if err := pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - t.Fatal("Failed to write data to cert.pem:", err) - } - - privBytes, err := x509.MarshalPKCS8PrivateKey(priv) - if err != nil { - t.Fatal("Unable to marshal private key:", err) - } - privPEM := &bytes.Buffer{} - if err := pem.Encode(privPEM, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { - t.Fatal("Failed to write data to key.pem:", err) - } - - name := test.ObjectNameForTest(t) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: test.ServingNamespace, - Labels: map[string]string{ - "test-secret": name, - }, - }, - Type: corev1.SecretTypeTLS, - StringData: map[string]string{ - corev1.TLSCertKey: certPEM.String(), - corev1.TLSPrivateKeyKey: privPEM.String(), - }, - } - t.Cleanup(func() { - clients.KubeClient.CoreV1().Secrets(secret.Namespace).Delete(ctx, secret.Name, metav1.DeleteOptions{}) - }) - if _, err := clients.KubeClient.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil { - t.Fatal("Error creating Secret:", err) - } - return name, &tls.Config{RootCAs: pool}, func() { - err := clients.KubeClient.CoreV1().Secrets(secret.Namespace).Delete(ctx, secret.Name, metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Error cleaning up Secret %s: %v", secret.Name, err) - } - } -} - -func getIngress() (string, string) { - // TODO: Gateway expose these info? - namespace := "istio-system" - if gatewayNsOverride := os.Getenv("GATEWAY_NAMESPACE_OVERRIDE"); gatewayNsOverride != "" { - namespace = gatewayNsOverride - } - name := "istio-ingressgateway" - if gatewayOverride := os.Getenv("GATEWAY_OVERRIDE"); gatewayOverride != "" { - name = gatewayOverride - } - return namespace, name -} - -func getClusterIngress() (string, string) { - // TODO: Gateway expose these info? - namespace := "istio-system" - if gatewayNsOverride := os.Getenv("LOCAL_GATEWAY_NAMESPACE_OVERRIDE"); gatewayNsOverride != "" { - namespace = gatewayNsOverride - } - name := "knative-local-gateway" - if gatewayOverride := os.Getenv("LOCAL_GATEWAY_OVERRIDE"); gatewayOverride != "" { - name = gatewayOverride - } - return namespace, name -} - -// CreateDialContext looks up the endpoint information to create a "dialer" for -// the provided HTTPRoute' public ingress loas balancer. It can be used to -// contact external-visibility services with an HTTP client via: -// -// client := &http.Client{ -// Transport: &http.Transport{ -// DialContext: CreateDialContext(t, clients), -// }, -// } -func CreateDialContext(ctx context.Context, t *testing.T, clients *test.Clients) func(context.Context, string, string) (net.Conn, error) { - t.Helper() - - namespace, name := getIngress() - - var svc *corev1.Service - err := reconciler.RetryTestErrors(func(attempts int) (err error) { - svc, err = clients.KubeClient.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{}) - return err - }) - if err != nil { - t.Fatalf("Unable to retrieve Kubernetes service %s/%s: %v", namespace, name, err) - } - - dial := network.NewBackoffDialer(dialBackoff) - if pkgTest.Flags.IngressEndpoint != "" { - t.Logf("ingressendpoint: %q", pkgTest.Flags.IngressEndpoint) - - // If we're using a manual --ingressendpoint then don't require - // "type: LoadBalancer", which may not play nice with KinD - return func(ctx context.Context, _ string, address string) (net.Conn, error) { - _, port, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - for _, sp := range svc.Spec.Ports { - if fmt.Sprint(sp.Port) == port { - return dial(ctx, "tcp", fmt.Sprintf("%s:%d", pkgTest.Flags.IngressEndpoint, sp.NodePort)) - } - } - return nil, fmt.Errorf("service doesn't contain a matching port: %s", port) - } - } else if len(svc.Status.LoadBalancer.Ingress) >= 1 { - ingress := svc.Status.LoadBalancer.Ingress[0] - return func(ctx context.Context, _ string, address string) (net.Conn, error) { - _, port, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - if ingress.IP != "" { - return dial(ctx, "tcp", ingress.IP+":"+port) - } - if ingress.Hostname != "" { - return dial(ctx, "tcp", ingress.Hostname+":"+port) - } - return nil, errors.New("service ingress does not contain dialing information") - } - } else { - t.Fatal("Service does not have a supported shape (not type LoadBalancer? missing --ingressendpoint?).") - - return nil // Unreachable - } -} - -type RequestOption func(*http.Request) -type ResponseExpectation func(response *http.Response) error - -func RuntimeRequest(ctx context.Context, t *testing.T, client *http.Client, url string, opts ...RequestOption) *types.RuntimeInfo { - var ri *types.RuntimeInfo - var retry bool - waitErr := wait.PollImmediate(test.PollInterval, test.PollTimeout, func() (bool, error) { - if ri, retry = RuntimeRequestWithExpectations(ctx, t, client, url, - []ResponseExpectation{StatusCodeExpectation(sets.NewInt(http.StatusOK))}, - false, - opts...); retry { - return false, nil - } - return true, nil - }) - if waitErr != nil { - t.Fatalf("failed to request: %v", waitErr) - } - return ri -} - -// RuntimeRequestWithExpectations attempts to make a request to url and return runtime information. -// If connection is successful only then it will validate all response expectations. -// If allowDialError is set to true then function will not fail if connection is a dial error. -func RuntimeRequestWithExpectations(ctx context.Context, t *testing.T, client *http.Client, url string, - responseExpectations []ResponseExpectation, - allowDialError bool, - opts ...RequestOption) (*types.RuntimeInfo, bool) { - t.Helper() - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - t.Error("Error creating Request:", err) - return nil, false - } - - for _, opt := range opts { - opt(req) - } - - resp, err := client.Do(req) - - if err != nil { - if !allowDialError || !IsDialError(err) { - t.Error("Error making GET request:", err) - } - return nil, false - } - - defer resp.Body.Close() - - // TODO: - // status in HTTPRoute is not implemented. - // Additionally, the "admitted" status would not work. see - https://kubernetes.slack.com/archives/CR0H13KGA/p1611865803004000 - if resp.StatusCode == http.StatusNotFound { - return nil, true - } - - for _, e := range responseExpectations { - if err := e(resp); err != nil { - t.Error("Error meeting response expectations:", err) - DumpResponse(ctx, t, resp) - return nil, false - } - } - - if resp.StatusCode == http.StatusOK { - b, err := io.ReadAll(resp.Body) - if err != nil { - t.Error("Unable to read response body:", err) - DumpResponse(ctx, t, resp) - return nil, false - } - ri := &types.RuntimeInfo{} - if err := json.Unmarshal(b, ri); err != nil { - t.Error("Unable to parse runtime image's response payload:", err) - return nil, false - } - return ri, false - } - return nil, false -} - -func DumpResponse(ctx context.Context, t *testing.T, resp *http.Response) { - t.Helper() - b, err := httputil.DumpResponse(resp, true) - if err != nil { - t.Error("Error dumping response:", err) - } - t.Log(string(b)) -} - -func StatusCodeExpectation(statusCodes sets.Int) ResponseExpectation { - return func(response *http.Response) error { - if !statusCodes.Has(response.StatusCode) { - return fmt.Errorf("got unexpected status: %d, expected %v", response.StatusCode, statusCodes) - } - return nil - } -} - -func IsDialError(err error) bool { - var errNetOp *net.OpError - if !errors.As(err, &errNetOp) { - return false - } - return errNetOp.Op == "dial" -} - -// WaitForHTTPRouteState polls the status of the Ingress called name from client every -// PollInterval until inState returns `true` indicating it is done, returns an -// error or PollTimeout. desc will be used to name the metric that is emitted to -// track how long it took for name to get into the state checked by inState. -func WaitForHTTPRouteState(ctx context.Context, client *test.GatewayAPIClients, name string, inState func(r *gatewayapi.HTTPRoute) (bool, error), desc string) error { - span := logging.GetEmitableSpan(ctx, fmt.Sprintf("WaitForHTTPRouteState/%s/%s", name, desc)) - defer span.End() - - var lastState *gatewayapi.HTTPRoute - waitErr := wait.PollImmediate(test.PollInterval, test.PollTimeout, func() (bool, error) { - err := reconciler.RetryTestErrors(func(attempts int) (err error) { - lastState, err = client.HTTPRoutes.Get(ctx, name, metav1.GetOptions{}) - return err - }) - if err != nil { - return true, err - } - return inState(lastState) - }) - - if waitErr != nil { - return fmt.Errorf("ingress %q is not in desired state, got: %+v: %w", name, lastState, waitErr) - } - return nil -} - -// IsHTTPRouteReady will check the status conditions of the ingress and return true if -// all gateways have been admitted. -func IsHTTPRouteReady(r *gatewayapi.HTTPRoute) (bool, error) { - if r.Status.Parents == nil { - return false, nil - } - for _, gw := range r.Status.Parents { - if !isGatewayAdmitted(gw) { - // Return false if _any_ of the gateways isn't admitted yet. - return false, nil - } - } - return true, nil -} - -func isGatewayAdmitted(gw gatewayapi.RouteParentStatus) bool { - for _, condition := range gw.Conditions { - if condition.Type == string(gatewayapi.RouteConditionAccepted) { - return condition.Status == metav1.ConditionTrue - } - } - return false -} - -func waitForBackend(t *testing.T, client *http.Client, url string) { - waitErr := wait.PollImmediate(test.PollInterval, test.PollTimeout, func() (bool, error) { - resp, err := client.Get(url) - if err != nil { - t.Fatal("Error making GET request:", err) - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - t.Logf("backend is not ready") - return false, nil - } - return true, nil - }) - if waitErr != nil { - t.Fatalf("failed to request: %v", waitErr) - } -} diff --git a/test/conformance/gateway-api/util_test.go b/test/conformance/gateway-api/util_test.go deleted file mode 100644 index bf3aa7ca5..000000000 --- a/test/conformance/gateway-api/util_test.go +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestIsHTTPRouteReady(t *testing.T) { - tests := []struct { - name string - expect bool - gatewayStatus []gatewayapi.RouteParentStatus - }{ - { - name: "Zero gateway - it does not have status condition", - }, { - name: "One gateway - it has Admitted condition true", - expect: true, - gatewayStatus: []gatewayapi.RouteParentStatus{{ - ParentRef: gatewayapi.ParentReference{Name: "foo", Namespace: namespacePtr("foo")}, - Conditions: []metav1.Condition{{ - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - }}, - }}, - }, { - name: "One gateway - it has Admitted condition false", - gatewayStatus: []gatewayapi.RouteParentStatus{{ - ParentRef: gatewayapi.ParentReference{Name: "foo", Namespace: namespacePtr("foo")}, - Conditions: []metav1.Condition{{ - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionFalse, - }}, - }}, - }, { - name: "One gateway - it does not have Admitted condition", - gatewayStatus: []gatewayapi.RouteParentStatus{{ - ParentRef: gatewayapi.ParentReference{Name: "foo", Namespace: namespacePtr("foo")}, - }}, - }, { - name: "Two gateways - both have Admitted condition true", - expect: true, - gatewayStatus: []gatewayapi.RouteParentStatus{ - { - ParentRef: gatewayapi.ParentReference{Name: "foo", Namespace: namespacePtr("foo")}, - Conditions: []metav1.Condition{ - { - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - }, - }, - }, { - ParentRef: gatewayapi.ParentReference{Name: "bar", Namespace: namespacePtr("bar")}, - Conditions: []metav1.Condition{ - { - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - }, - }, - }, - }, - }, { - name: "Two gateways - one has Admitted condition false", - gatewayStatus: []gatewayapi.RouteParentStatus{ - { - ParentRef: gatewayapi.ParentReference{Name: "foo", Namespace: namespacePtr("foo")}, - Conditions: []metav1.Condition{ - { - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionFalse, - }, - }, - }, { - ParentRef: gatewayapi.ParentReference{Name: "bar", Namespace: namespacePtr("bar")}, - Conditions: []metav1.Condition{ - { - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - }, - }, - }, - }, - }, { - name: "Two gateways - one does not have Admitted condition", - gatewayStatus: []gatewayapi.RouteParentStatus{ - { - ParentRef: gatewayapi.ParentReference{Name: "foo", Namespace: namespacePtr("foo")}, - Conditions: []metav1.Condition{ - { - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionFalse, - }, - }, - }, { - ParentRef: gatewayapi.ParentReference{Name: "bar", Namespace: namespacePtr("bar")}, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - httpRoute := &gatewayapi.HTTPRoute{ - Status: gatewayapi.HTTPRouteStatus{ - RouteStatus: gatewayapi.RouteStatus{Parents: test.gatewayStatus}, - }, - } - got, _ := IsHTTPRouteReady(httpRoute) - if got != test.expect { - t.Errorf("Got = %v, want = %v", got, test.expect) - } - }) - } -} diff --git a/test/conformance/gateway-api/visibility.go b/test/conformance/gateway-api/visibility.go deleted file mode 100644 index 3ef333ed5..000000000 --- a/test/conformance/gateway-api/visibility.go +++ /dev/null @@ -1,421 +0,0 @@ -/* -Copyright 2020 The Knative 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 ingress - -import ( - "context" - "errors" - "fmt" - "math" - "net/http" - "testing" - - "golang.org/x/sync/errgroup" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/pointer" - "knative.dev/net-gateway-api/test" - "knative.dev/networking/pkg/apis/networking" - nettest "knative.dev/networking/test" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestVisibility(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - // Create the private backend - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // Generate a different hostname for each of these tests, so that they do not fail when run concurrently. - var privateHostNames = map[string]gatewayapi.Hostname{ - "fqdn": gatewayapi.Hostname(test.ObjectNameForTest(t) + "." + test.ServingNamespace + ".svc." + nettest.NetworkingFlags.ClusterSuffix), - "short": gatewayapi.Hostname(test.ObjectNameForTest(t) + "." + test.ServingNamespace + ".svc"), - "shortest": gatewayapi.Hostname(test.ObjectNameForTest(t) + "." + test.ServingNamespace), - } - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testLocalGateway, - }}, - Hostnames: []gatewayapi.Hostname{privateHostNames["fqdn"], privateHostNames["short"], privateHostNames["shortest"]}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}, - }) - - // Ensure the service is not publicly accessible - for _, privateHostName := range privateHostNames { - RuntimeRequestWithExpectations(ctx, t, client, "http://"+string(privateHostName), []ResponseExpectation{StatusCodeExpectation(sets.NewInt(http.StatusNotFound))}, true) - } - - for name := range privateHostNames { - privateHostName := privateHostNames[name] // avoid the Go iterator capture issue. - t.Run(name, func(t *testing.T) { - t.Parallel() - - testProxyToHelloworld(ctx, t, clients, string(privateHostName)) - }) - } -} - -func testProxyToHelloworld(ctx context.Context, t *testing.T, clients *test.Clients, privateHostName string) { - - namespace, name := getClusterIngress() - loadbalancerAddress := fmt.Sprintf("%s.%s.svc.%s", name, namespace, nettest.NetworkingFlags.ClusterSuffix) - proxyName, proxyPort, _ := CreateProxyService(ctx, t, clients, privateHostName, loadbalancerAddress) - - // Using fixed hostnames can lead to conflicts when -count=N>1 - // so pseudo-randomize the hostnames to avoid conflicts. - publicHostName := gatewayapi.Hostname(test.ObjectNameForTest(t) + ".publicproxy.example.com") - - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{publicHostName}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(proxyPort), - Name: gatewayapi.ObjectName(proxyName), - }}}, - }, - }}, - }) - - // Ensure the service is accessible from within the cluster. - RuntimeRequest(ctx, t, client, "http://"+string(publicHostName)) -} - -func TestVisibilitySplit(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - // Use a post-split injected header to establish which split we are sending traffic to. - const headerName = "Foo-Bar-Baz" - - backends := make([]gatewayapi.HTTPBackendRef, 0, 10) - weights := make(map[string]float64, len(backends)) - - // Double the percentage of the split each iteration until it would overflow, and then - // give the last route the remainder. - percent, total := int32(1), int32(0) - for i := 0; i < 10; i++ { - weight := percent - name, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - backends = append(backends, - gatewayapi.HTTPBackendRef{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }, - Weight: &weight, - }, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: name, - }}, - }}, - }}, - ) - weights[name] = float64(percent) - - total += percent - percent *= 2 - // Cap the final non-zero bucket so that we total 100% - // After that, this will zero out remaining buckets. - if total+percent > 100 { - percent = 100 - total - } - } - - name := test.ObjectNameForTest(t) - - // Create a simple Ingress over the 10 Services. - privateHostName := fmt.Sprintf("%s.%s.svc.%s", name, test.ServingNamespace, nettest.NetworkingFlags.ClusterSuffix) - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testLocalGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(privateHostName)}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: backends, - }}, - }) - - // Ensure we can't connect to the private resources - RuntimeRequestWithExpectations(ctx, t, client, "http://"+privateHostName, []ResponseExpectation{StatusCodeExpectation(sets.NewInt(http.StatusNotFound))}, true) - - namespace, name := getClusterIngress() - loadbalancerAddress := fmt.Sprintf("%s.%s.svc.%s", name, namespace, nettest.NetworkingFlags.ClusterSuffix) - proxyName, proxyPort, _ := CreateProxyService(ctx, t, clients, privateHostName, loadbalancerAddress) - - publicHostName := fmt.Sprintf("%s.%s", name, "example.com") - _, client, _ = CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(publicHostName)}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(proxyPort), - Name: gatewayapi.ObjectName(proxyName), - }}}, - }, - }}, - }) - - // Create a large enough population of requests that we can reasonably assess how - // well the Ingress respected the percentage split. - seen := make(map[string]float64, len(backends)) - - const ( - // The total number of requests to make (as a float to avoid conversions in later computations). - totalRequests = 1000.0 - // The increment to make for each request, so that the values of seen reflect the - // percentage of the total number of requests we are making. - increment = 100.0 / totalRequests - // Allow the Ingress to be within 10% of the configured value. - margin = 10.0 - ) - var g errgroup.Group - g.SetLimit(8) - resultCh := make(chan string, totalRequests) - - for i := 0.0; i < totalRequests; i++ { - g.Go(func() error { - ri := RuntimeRequest(ctx, t, client, "http://"+publicHostName) - if ri == nil { - return errors.New("failed to request") - } - resultCh <- ri.Request.Headers.Get(headerName) - return nil - }) - } - if err := g.Wait(); err != nil { - t.Error("Error while sending requests:", err) - } - close(resultCh) - - for r := range resultCh { - seen[r] += increment - } - - for name, want := range weights { - got := seen[name] - switch { - case want == 0.0 && got > 0.0: - // For 0% targets, we have tighter requirements. - t.Errorf("Target %q received traffic, wanted none (0%% target).", name) - case math.Abs(got-want) > margin: - t.Errorf("Target %q received %f%%, wanted %f +/- %f", name, got, want, margin) - } - } -} - -func TestVisibilityPath(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - // For /foo - fooName, fooPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // For /bar - barName, barPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // For /baz - bazName, bazPort, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - mainName, port, _ := CreateRuntimeService(ctx, t, clients, networking.ServicePortNameHTTP1) - - // Use a post-split injected header to establish which split we are sending traffic to. - const headerName = "Which-Backend" - - name := test.ObjectNameForTest(t) - privateHostName := fmt.Sprintf("%s.%s.svc.%s", name, test.ServingNamespace, nettest.NetworkingFlags.ClusterSuffix) - _, client, _ := CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testLocalGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(privateHostName)}, - Rules: []gatewayapi.HTTPRouteRule{ - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(fooPort), - Name: gatewayapi.ObjectName(fooName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: fooName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/foo"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(barPort), - Name: gatewayapi.ObjectName(barName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: barName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/bar"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(bazPort), - Name: gatewayapi.ObjectName(bazName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: bazName, - }}, - }}, - }, - }}, - Matches: []gatewayapi.HTTPRouteMatch{{ - Path: &gatewayapi.HTTPPathMatch{ - Type: pathMatchTypePtr(gatewayapi.PathMatchPathPrefix), - Value: pointer.String("/baz"), - }, - }}, - }, - { - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(mainName), - }}, - // Append different headers to each split, which lets us identify - // which backend we hit. - Filters: []gatewayapi.HTTPRouteFilter{{ - Type: gatewayapi.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayapi.HTTPRequestHeaderFilter{ - Set: []gatewayapi.HTTPHeader{{ - Name: headerName, - Value: mainName, - }}, - }}, - }, - }}, - }, - }, - }) - - // Ensure we can't connect to the private resources - for _, path := range []string{"", "/foo", "/bar", "/baz"} { - RuntimeRequestWithExpectations(ctx, t, client, "http://"+privateHostName+path, []ResponseExpectation{StatusCodeExpectation(sets.NewInt(http.StatusNotFound))}, true) - } - - namespace, name := getClusterIngress() - loadbalancerAddress := fmt.Sprintf("%s.%s.svc.%s", name, namespace, nettest.NetworkingFlags.ClusterSuffix) - proxyName, proxyPort, _ := CreateProxyService(ctx, t, clients, privateHostName, loadbalancerAddress) - - publicHostName := fmt.Sprintf("%s.%s", name, "example.com") - _, client, _ = CreateHTTPRouteReady(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(publicHostName)}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(proxyPort), - Name: gatewayapi.ObjectName(proxyName), - }}}, - }, - }}, - }) - - tests := map[string]string{ - "/foo": fooName, - "/bar": barName, - "/baz": bazName, - "": mainName, - "/asdf": mainName, - } - - for path, want := range tests { - t.Run(path, func(t *testing.T) { - t.Parallel() - - ri := RuntimeRequest(ctx, t, client, "http://"+publicHostName+path) - if ri == nil { - return - } - - got := ri.Request.Headers.Get(headerName) - if got != want { - t.Errorf("Header[%q] = %q, wanted %q", headerName, got, want) - } - }) - } -} diff --git a/test/conformance/gateway-api/websocket.go b/test/conformance/gateway-api/websocket.go deleted file mode 100644 index 7064e4c59..000000000 --- a/test/conformance/gateway-api/websocket.go +++ /dev/null @@ -1,209 +0,0 @@ -/* -Copyright 2021 The Knative 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 ingress - -import ( - "context" - "fmt" - "math/rand" - "net/http" - "net/url" - "strings" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/gorilla/websocket" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/utils/pointer" - "knative.dev/net-gateway-api/test" - gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -// TestWebsocket verifies that websockets may be used via a simple Ingress. -func TestWebsocket(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - const suffix = "- pong" - name, port, _ := CreateWebsocketService(ctx, t, clients, suffix) - - domain := name + ".example.com" - - // Create a simple Ingress over the Service. - _, dialCtx, _ := createHTTPRouteReadyDialContext(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(domain)}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{{ - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(port), - Name: gatewayapi.ObjectName(name), - }}}, - }, - }}}) - - dialer := websocket.Dialer{ - NetDialContext: dialCtx, - Proxy: http.ProxyFromEnvironment, - HandshakeTimeout: 45 * time.Second, - } - - u := url.URL{Scheme: "ws", Host: domain, Path: "/"} - - // TODO: As Ingress v2 does not have prober, it needs to make sure backend is ready. - // As Ingress v2 does not have prober, it needs to make sure backend is ready. - client := &http.Client{Transport: &uaRoundTripper{RoundTripper: &http.Transport{DialContext: dialCtx}}} - waitForBackend(t, client, "http://"+domain) - - conn, _, err := dialer.Dial(u.String(), http.Header{"Host": {domain}}) - if err != nil { - t.Fatal("Dial() =", err) - } - defer conn.Close() - - for i := 0; i < 100; i++ { - checkWebsocketRoundTrip(ctx, t, conn, suffix) - } -} - -// TestWebsocketSplit verifies that websockets may be used across a traffic split. -func TestWebsocketSplit(t *testing.T) { - t.Parallel() - ctx, clients := context.Background(), test.Setup(t) - - const suffixBlue = "- blue" - blueName, bluePort, _ := CreateWebsocketService(ctx, t, clients, suffixBlue) - - const suffixGreen = "- green" - greenName, greenPort, _ := CreateWebsocketService(ctx, t, clients, suffixGreen) - - // The suffixes we expect to see. - want := sets.NewString(suffixBlue, suffixGreen) - - // Create a simple HTTPRoute over the Service. - name := test.ObjectNameForTest(t) - domain := name + ".example.com" - _, dialCtx, _ := createHTTPRouteReadyDialContext(ctx, t, clients, gatewayapi.HTTPRouteSpec{ - CommonRouteSpec: gatewayapi.CommonRouteSpec{ParentRefs: []gatewayapi.ParentReference{ - testGateway, - }}, - Hostnames: []gatewayapi.Hostname{gatewayapi.Hostname(domain)}, - Rules: []gatewayapi.HTTPRouteRule{{ - BackendRefs: []gatewayapi.HTTPBackendRef{ - { - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(bluePort), - Name: gatewayapi.ObjectName(blueName), - }, - Weight: pointer.Int32(1), - }, - }, - { - BackendRef: gatewayapi.BackendRef{ - BackendObjectReference: gatewayapi.BackendObjectReference{ - Port: portNumPtr(greenPort), - Name: gatewayapi.ObjectName(greenName), - }, - Weight: pointer.Int32(1), - }, - }, - }, - }}}) - dialer := websocket.Dialer{ - NetDialContext: dialCtx, - Proxy: http.ProxyFromEnvironment, - HandshakeTimeout: 45 * time.Second, - } - - u := url.URL{Scheme: "ws", Host: domain, Path: "/"} - - // TODO: As Ingress v2 does not have prober, it needs to make sure backend is ready. - // As Ingress v2 does not have prober, it needs to make sure backend is ready. - client := &http.Client{Transport: &uaRoundTripper{RoundTripper: &http.Transport{DialContext: dialCtx}}} - waitForBackend(t, client, "http://"+domain) - - const maxRequests = 100 - got := sets.NewString() - for i := 0; i < maxRequests; i++ { - conn, _, err := dialer.Dial(u.String(), http.Header{"Host": {domain}}) - if err != nil { - t.Fatal("Dial() =", err) - } - defer conn.Close() - - suffix := findWebsocketSuffix(ctx, t, conn) - if suffix == "" { - continue - } - got.Insert(suffix) - - for j := 0; j < 10; j++ { - checkWebsocketRoundTrip(ctx, t, conn, suffix) - } - - if want.Equal(got) { - // Short circuit if we've seen all splits. - return - } - } - - // Us getting here means we haven't seen splits. - t.Errorf("(over %d requests) (-want, +got) = %s", maxRequests, cmp.Diff(want.List(), got.List())) -} - -func findWebsocketSuffix(ctx context.Context, t *testing.T, conn *websocket.Conn) string { - t.Helper() - // Establish the suffix that corresponds to this socket. - message := fmt.Sprint("ping -", rand.Intn(1000)) - if err := conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil { - t.Error("WriteMessage() =", err) - return "" - } - - _, recv, err := conn.ReadMessage() - if err != nil { - t.Error("ReadMessage() =", err) - return "" - } - gotMsg := string(recv) - if !strings.HasPrefix(gotMsg, message) { - t.Errorf("ReadMessage() = %s, wanted %s prefix", gotMsg, message) - return "" - } - return strings.TrimSpace(strings.TrimPrefix(gotMsg, message)) -} - -func checkWebsocketRoundTrip(ctx context.Context, t *testing.T, conn *websocket.Conn, suffix string) { - t.Helper() - message := fmt.Sprint("ping -", rand.Intn(1000)) - if err := conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil { - t.Error("WriteMessage() =", err) - return - } - - // Read back the echoed message and compared with sent. - if _, recv, err := conn.ReadMessage(); err != nil { - t.Error("ReadMessage() =", err) - } else if got, want := string(recv), message+" "+suffix; got != want { - t.Errorf("ReadMessage() = %s, wanted %s", got, want) - } -} diff --git a/third_party/VENDOR-LICENSE/github.com/gorilla/websocket/LICENSE b/third_party/VENDOR-LICENSE/github.com/gorilla/websocket/LICENSE deleted file mode 100644 index 9171c9722..000000000 --- a/third_party/VENDOR-LICENSE/github.com/gorilla/websocket/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.