Skip to content

Commit 6696019

Browse files
authored
Merge pull request #2034 from LiorLieberman/release-0.7
[release-0.7] Request mirroring conformance (#1912)
2 parents bd3d0ed + 94f8d78 commit 6696019

File tree

8 files changed

+239
-3
lines changed

8 files changed

+239
-3
lines changed

conformance/conformance_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,12 @@ func TestConformance(t *testing.T) {
5959
*flags.GatewayClassName, *flags.CleanupBaseResources, *flags.ShowDebug, *flags.EnableAllSupportedFeatures, *flags.SupportedFeatures, *flags.ExemptFeatures)
6060

6161
cSuite := suite.New(suite.Options{
62-
Client: client,
63-
RESTClient: clientset.CoreV1().RESTClient().(*rest.RESTClient),
64-
RestConfig: cfg,
62+
Client: client,
63+
RESTClient: clientset.CoreV1().RESTClient().(*rest.RESTClient),
64+
RestConfig: cfg,
65+
// This clientset is needed in addition to the client only because
66+
// controller-runtime client doesn't support non CRUD sub-resources yet (https://github.com/kubernetes-sigs/controller-runtime/issues/452).
67+
Clientset: clientset,
6568
GatewayClassName: *flags.GatewayClassName,
6669
Debug: *flags.ShowDebug,
6770
CleanupBaseResources: *flags.CleanupBaseResources,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package tests
18+
19+
import (
20+
"testing"
21+
22+
"k8s.io/apimachinery/pkg/types"
23+
24+
"sigs.k8s.io/gateway-api/conformance/utils/http"
25+
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
26+
"sigs.k8s.io/gateway-api/conformance/utils/suite"
27+
)
28+
29+
func init() {
30+
ConformanceTests = append(ConformanceTests, HTTPRouteRequestMirror)
31+
}
32+
33+
var HTTPRouteRequestMirror = suite.ConformanceTest{
34+
ShortName: "HTTPRouteRequestMirror",
35+
Description: "An HTTPRoute with request mirror filter",
36+
Manifests: []string{"tests/httproute-request-mirror.yaml"},
37+
Features: []suite.SupportedFeature{
38+
suite.SupportGateway,
39+
suite.SupportHTTPRoute,
40+
suite.SupportHTTPRouteRequestMirror,
41+
},
42+
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
43+
ns := "gateway-conformance-infra"
44+
routeNN := types.NamespacedName{Name: "request-mirror", Namespace: ns}
45+
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
46+
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
47+
48+
testCases := []http.ExpectedResponse{
49+
{
50+
Request: http.Request{
51+
Path: "/mirror",
52+
},
53+
ExpectedRequest: &http.ExpectedRequest{
54+
Request: http.Request{
55+
Path: "/mirror",
56+
},
57+
},
58+
Backend: "infra-backend-v1",
59+
MirroredTo: "infra-backend-v2",
60+
Namespace: ns,
61+
},
62+
}
63+
for i := range testCases {
64+
// Declare tc here to avoid loop variable
65+
// reuse issues across parallel tests.
66+
tc := testCases[i]
67+
t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
68+
t.Parallel()
69+
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc)
70+
http.ExpectMirroredRequest(t, suite.Client, suite.Clientset, ns, tc.MirroredTo, tc.Request.Path)
71+
})
72+
}
73+
},
74+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
apiVersion: gateway.networking.k8s.io/v1beta1
2+
kind: HTTPRoute
3+
metadata:
4+
name: request-mirror
5+
namespace: gateway-conformance-infra
6+
spec:
7+
parentRefs:
8+
- name: same-namespace
9+
rules:
10+
- matches:
11+
- path:
12+
type: PathPrefix
13+
value: /mirror
14+
filters:
15+
- type: RequestMirror
16+
requestMirror:
17+
backendRef:
18+
name: infra-backend-v2
19+
namespace: gateway-conformance-infra
20+
port: 8080
21+
backendRefs:
22+
- name: infra-backend-v1
23+
port: 8080
24+
namespace: gateway-conformance-infra

conformance/utils/http/http.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ type ExpectedResponse struct {
5252
Backend string
5353
Namespace string
5454

55+
// MirroredTo is the destination pod of the mirrored request.
56+
MirroredTo string
57+
5558
// User Given TestCase name
5659
TestCaseName string
5760
}

conformance/utils/http/mirror.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package http
18+
19+
import (
20+
"fmt"
21+
"regexp"
22+
"testing"
23+
"time"
24+
25+
"github.com/stretchr/testify/require"
26+
clientset "k8s.io/client-go/kubernetes"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
30+
)
31+
32+
func ExpectMirroredRequest(t *testing.T, client client.Client, clientset *clientset.Clientset, ns, mirrorPod, path string) {
33+
if mirrorPod == "" {
34+
t.Fatalf("MirroredTo wasn't provided in the testcase, this test should only check http request mirror.")
35+
}
36+
37+
require.Eventually(t, func() bool {
38+
var mirrored bool
39+
mirrorLogRegexp := regexp.MustCompile(fmt.Sprintf("Echoing back request made to \\%s to client", path))
40+
41+
t.Log("Searching for the mirrored request log")
42+
t.Logf("Reading \"%s/%s\" logs", ns, mirrorPod)
43+
logs, err := kubernetes.DumpEchoLogs(ns, mirrorPod, client, clientset)
44+
if err != nil {
45+
t.Logf("could not read \"%s/%s\" logs: %v", ns, mirrorPod, err)
46+
return false
47+
}
48+
49+
for _, log := range logs {
50+
if mirrorLogRegexp.MatchString(string(log)) {
51+
mirrored = true
52+
break
53+
}
54+
}
55+
return mirrored
56+
57+
}, 60*time.Second, time.Second, "Mirrored request log wasn't found")
58+
59+
t.Log("Mirrored request log found")
60+
}

conformance/utils/kubernetes/logs.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package kubernetes
18+
19+
import (
20+
"context"
21+
"io"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
"k8s.io/apimachinery/pkg/labels"
25+
clientset "k8s.io/client-go/kubernetes"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
)
28+
29+
// DumpEchoLogs returns logs of the echoserver pod in
30+
// in the given namespace and with the given name.
31+
func DumpEchoLogs(ns, name string, c client.Client, cs *clientset.Clientset) ([][]byte, error) {
32+
var logs [][]byte
33+
34+
pods := new(corev1.PodList)
35+
podListOptions := &client.ListOptions{
36+
LabelSelector: labels.SelectorFromSet(map[string]string{"app": name}),
37+
Namespace: ns,
38+
}
39+
if err := c.List(context.TODO(), pods, podListOptions); err != nil {
40+
return nil, err
41+
}
42+
43+
podLogOptions := &corev1.PodLogOptions{
44+
Container: name,
45+
}
46+
for _, pod := range pods.Items {
47+
if pod.Status.Phase == corev1.PodFailed {
48+
continue
49+
}
50+
req := cs.CoreV1().Pods(ns).GetLogs(pod.Name, podLogOptions)
51+
logStream, err := req.Stream(context.TODO())
52+
if err != nil {
53+
continue
54+
}
55+
defer logStream.Close()
56+
logBytes, err := io.ReadAll(logStream)
57+
if err != nil {
58+
continue
59+
}
60+
logs = append(logs, logBytes)
61+
}
62+
63+
return logs, nil
64+
}

conformance/utils/suite/features.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ const (
137137

138138
// This option indicates support for HTTPRoute path rewrite (experimental conformance)
139139
SupportHTTPRoutePathRewrite SupportedFeature = "HTTPRoutePathRewrite"
140+
141+
// This option indicates support for HTTPRoute request mirror (extended conformance).
142+
SupportHTTPRouteRequestMirror SupportedFeature = "HTTPRouteRequestMirror"
140143
)
141144

142145
// HTTPExtendedFeatures includes all the supported features for HTTPRoute
@@ -151,6 +154,7 @@ var HTTPExtendedFeatures = sets.New(
151154
SupportHTTPRoutePathRedirect,
152155
SupportHTTPRouteHostRewrite,
153156
SupportHTTPRoutePathRewrite,
157+
SupportHTTPRouteRequestMirror,
154158
).Insert(HTTPCoreFeatures.UnsortedList()...)
155159

156160
// -----------------------------------------------------------------------------

conformance/utils/suite/suite.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"testing"
2222

2323
"k8s.io/apimachinery/pkg/util/sets"
24+
clientset "k8s.io/client-go/kubernetes"
2425
"k8s.io/client-go/rest"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
2627

@@ -34,6 +35,7 @@ import (
3435
// conformance tests.
3536
type ConformanceTestSuite struct {
3637
Client client.Client
38+
Clientset *clientset.Clientset
3739
RESTClient *rest.RESTClient
3840
RestConfig *rest.Config
3941
RoundTripper roundtripper.RoundTripper
@@ -53,6 +55,7 @@ type ConformanceTestSuite struct {
5355
// Options can be used to initialize a ConformanceTestSuite.
5456
type Options struct {
5557
Client client.Client
58+
Clientset *clientset.Clientset
5659
RESTClient *rest.RESTClient
5760
RestConfig *rest.Config
5861
GatewayClassName string
@@ -105,6 +108,7 @@ func New(s Options) *ConformanceTestSuite {
105108

106109
suite := &ConformanceTestSuite{
107110
Client: s.Client,
111+
Clientset: s.Clientset,
108112
RESTClient: s.RESTClient,
109113
RestConfig: s.RestConfig,
110114
RoundTripper: roundTripper,

0 commit comments

Comments
 (0)