Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions conformance/tests/httproute-request-mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ var HTTPRouteRequestMirror = suite.ConformanceTest{
Path: "/mirror",
},
},
Backend: "infra-backend-v1",
MirroredTo: "infra-backend-v2",
Namespace: ns,
}, {
Backend: "infra-backend-v1",
MirroredTo: []http.BackendRef{{
Name: "infra-backend-v2",
Namespace: ns,
}},
Namespace: ns,
},
{
Request: http.Request{
Path: "/mirror-and-modify-headers",
Headers: map[string]string{
Expand All @@ -77,9 +81,12 @@ var HTTPRouteRequestMirror = suite.ConformanceTest{
},
AbsentHeaders: []string{"X-Header-Remove"},
},
Namespace: ns,
Backend: "infra-backend-v1",
MirroredTo: "infra-backend-v2",
Namespace: ns,
Backend: "infra-backend-v1",
MirroredTo: []http.BackendRef{{
Name: "infra-backend-v2",
Namespace: ns,
}},
},
}
for i := range testCases {
Expand All @@ -89,7 +96,7 @@ var HTTPRouteRequestMirror = suite.ConformanceTest{
t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
t.Parallel()
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc)
http.ExpectMirroredRequest(t, suite.Client, suite.Clientset, ns, tc.MirroredTo, tc.Request.Path)
http.ExpectMirroredRequest(t, suite.Client, suite.Clientset, tc.MirroredTo, tc.Request.Path)
})
}
},
Expand Down
115 changes: 115 additions & 0 deletions conformance/tests/httproute-request-multiple-mirrors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tests

import (
"testing"

"k8s.io/apimachinery/pkg/types"

"sigs.k8s.io/gateway-api/conformance/utils/http"
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
"sigs.k8s.io/gateway-api/conformance/utils/suite"
)

func init() {
ConformanceTests = append(ConformanceTests, HTTPRouteRequestMultipleMirrors)
}

var HTTPRouteRequestMultipleMirrors = suite.ConformanceTest{
ShortName: "HTTPRouteRequestMultipleMirrors",
Description: "An HTTPRoute with multiple request mirror filters",
Manifests: []string{"tests/httproute-request-multiple-mirrors.yaml"},
Features: []suite.SupportedFeature{
suite.SupportGateway,
suite.SupportHTTPRoute,
suite.SupportHTTPRouteRequestMirror,
suite.SupportHTTPRouteRequestMultipleMirrors,
},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "request-multiple-mirrors", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)

testCases := []http.ExpectedResponse{
{
Request: http.Request{
Path: "/mirror",
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/mirror",
},
},
Backend: "infra-backend-v1",
MirroredTo: []http.BackendRef{
{
Name: "infra-backend-v2",
Namespace: ns,
},
{
Name: "infra-backend-v3",
Namespace: "another-namespace",
},
},
Namespace: ns,
Comment on lines +59 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a requirement for this PR, but I am curious if changing Backend to the new BackendRef struct you created is something we want and how much effort it will take (since it is widely used in a lot of tests).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if there is a demand for it I can address it in a follow-up PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good if you can do a quick check and see if its something we would want and if so, open an issue for it (it can be a good-first-issue imo)

}, {
Request: http.Request{
Path: "/mirror-and-modify-request-headers",
Headers: map[string]string{
"X-Header-Remove": "remove-val",
"X-Header-Add-Append": "append-val-1",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/mirror-and-modify-request-headers",
Headers: map[string]string{
"X-Header-Add": "header-val-1",
"X-Header-Add-Append": "append-val-1,header-val-2",
"X-Header-Set": "set-overwrites-values",
},
},
AbsentHeaders: []string{"X-Header-Remove"},
},
Namespace: ns,
Backend: "infra-backend-v1",
MirroredTo: []http.BackendRef{
{
Name: "infra-backend-v2",
Namespace: ns,
},
{
Name: "infra-backend-v3",
Namespace: "another-namespace",
},
},
},
}
for i := range testCases {
// Declare tc here to avoid loop variable
// reuse issues across parallel tests.
tc := testCases[i]
t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
t.Parallel()
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc)
http.ExpectMirroredRequest(t, suite.Client, suite.Clientset, tc.MirroredTo, tc.Request.Path)
})
}
},
}
63 changes: 63 additions & 0 deletions conformance/tests/httproute-request-multiple-mirrors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: request-multiple-mirrors
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- matches:
- path:
type: PathPrefix
value: /mirror
filters:
- type: RequestMirror
requestMirror:
backendRef:
name: infra-backend-v2
namespace: gateway-conformance-infra
port: 8080
- type: RequestMirror
requestMirror:
backendRef:
name: infra-backend-v3
namespace: another-namespace
port: 8080
backendRefs:
- name: infra-backend-v1
port: 8080
namespace: gateway-conformance-infra
- matches:
- path:
type: PathPrefix
value: /mirror-and-modify-request-headers
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
set:
- name: X-Header-Set
value: set-overwrites-values
add:
- name: X-Header-Add
value: header-val-1
- name: X-Header-Add-Append
value: header-val-2
remove:
- X-Header-Remove
- type: RequestMirror
requestMirror:
backendRef:
name: infra-backend-v2
namespace: gateway-conformance-infra
port: 8080
- type: RequestMirror
requestMirror:
backendRef:
name: infra-backend-v3
namespace: another-namespace
port: 8080
backendRefs:
- name: infra-backend-v1
port: 8080
namespace: gateway-conformance-infra
9 changes: 7 additions & 2 deletions conformance/utils/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ type ExpectedResponse struct {
Backend string
Namespace string

// MirroredTo is the destination pod of the mirrored request.
MirroredTo string
// MirroredTo is the destination BackendRefs of the mirrored request.
MirroredTo []BackendRef

// User Given TestCase name
TestCaseName string
Expand Down Expand Up @@ -87,6 +87,11 @@ type Response struct {
AbsentHeaders []string
}

type BackendRef struct {
Name string
Namespace string
}

// MakeRequestAndExpectEventuallyConsistentResponse makes a request with the given parameters,
// understanding that the request may fail for some amount of time.
//
Expand Down
58 changes: 36 additions & 22 deletions conformance/utils/http/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package http
import (
"fmt"
"regexp"
"sync"
"testing"
"time"

Expand All @@ -29,31 +30,44 @@ import (
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
)

func ExpectMirroredRequest(t *testing.T, client client.Client, clientset clientset.Interface, ns, mirrorPod, path string) {
if mirrorPod == "" {
t.Fatalf("MirroredTo wasn't provided in the testcase, this test should only check http request mirror.")
func ExpectMirroredRequest(t *testing.T, client client.Client, clientset clientset.Interface, mirrorPods []BackendRef, path string) {
for i, mirrorPod := range mirrorPods {
if mirrorPod.Name == "" {
t.Fatalf("Mirrored BackendRef[%d].Name wasn't provided in the testcase, this test should only check http request mirror.", i)
}
}

require.Eventually(t, func() bool {
var mirrored bool
mirrorLogRegexp := regexp.MustCompile(fmt.Sprintf("Echoing back request made to \\%s to client", path))
var wg sync.WaitGroup
wg.Add(len(mirrorPods))

t.Log("Searching for the mirrored request log")
t.Logf("Reading \"%s/%s\" logs", ns, mirrorPod)
logs, err := kubernetes.DumpEchoLogs(ns, mirrorPod, client, clientset)
if err != nil {
t.Logf("could not read \"%s/%s\" logs: %v", ns, mirrorPod, err)
return false
}
for _, mirrorPod := range mirrorPods {
go func(mirrorPod BackendRef) {
defer wg.Done()

for _, log := range logs {
if mirrorLogRegexp.MatchString(string(log)) {
mirrored = true
break
}
}
return mirrored
}, 60*time.Second, time.Second, "Mirrored request log wasn't found")
require.Eventually(t, func() bool {
var mirrored bool
mirrorLogRegexp := regexp.MustCompile(fmt.Sprintf("Echoing back request made to \\%s to client", path))

t.Log("Searching for the mirrored request log")
t.Logf(`Reading "%s/%s" logs`, mirrorPod.Namespace, mirrorPod.Name)
logs, err := kubernetes.DumpEchoLogs(mirrorPod.Namespace, mirrorPod.Name, client, clientset)
if err != nil {
t.Logf(`Couldn't read "%s/%s" logs: %v`, mirrorPod.Namespace, mirrorPod.Name, err)
return false
}

for _, log := range logs {
if mirrorLogRegexp.MatchString(string(log)) {
mirrored = true
break
}
}
return mirrored
}, 60*time.Second, time.Second, fmt.Sprintf(`Couldn't find mirrored request in "%s/%s" logs`, mirrorPod.Namespace, mirrorPod.Name))
}(mirrorPod)
}

wg.Wait()

t.Log("Mirrored request log found")
t.Log("Found mirrored request log in all desired backends")
}
4 changes: 4 additions & 0 deletions conformance/utils/suite/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ const (

// This option indicates support for HTTPRoute request mirror (extended conformance).
SupportHTTPRouteRequestMirror SupportedFeature = "HTTPRouteRequestMirror"

// This option indicates support for multiple RequestMirror filters within the same HTTPRoute rule (extended conformance).
SupportHTTPRouteRequestMultipleMirrors SupportedFeature = "HTTPRouteRequestMultipleMirrors"
)

// HTTPRouteExtendedFeatures includes all the supported features for HTTPRoute
Expand All @@ -133,6 +136,7 @@ var HTTPRouteExtendedFeatures = sets.New(
SupportHTTPRouteHostRewrite,
SupportHTTPRoutePathRewrite,
SupportHTTPRouteRequestMirror,
SupportHTTPRouteRequestMultipleMirrors,
)

// -----------------------------------------------------------------------------
Expand Down