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
9 changes: 6 additions & 3 deletions conformance/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ func TestConformance(t *testing.T) {
*flags.GatewayClassName, *flags.CleanupBaseResources, *flags.ShowDebug, *flags.EnableAllSupportedFeatures, *flags.SupportedFeatures, *flags.ExemptFeatures)

cSuite := suite.New(suite.Options{
Client: client,
RESTClient: clientset.CoreV1().RESTClient().(*rest.RESTClient),
RestConfig: cfg,
Client: client,
RESTClient: clientset.CoreV1().RESTClient().(*rest.RESTClient),
RestConfig: cfg,
// This clientset is needed in addition to the client only because
// controller-runtime client doesn't support non CRUD sub-resources yet (https://github.com/kubernetes-sigs/controller-runtime/issues/452).
Clientset: clientset,
GatewayClassName: *flags.GatewayClassName,
Debug: *flags.ShowDebug,
CleanupBaseResources: *flags.CleanupBaseResources,
Expand Down
74 changes: 74 additions & 0 deletions conformance/tests/httproute-request-mirror.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
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, HTTPRouteRequestMirror)
}

var HTTPRouteRequestMirror = suite.ConformanceTest{
ShortName: "HTTPRouteRequestMirror",
Description: "An HTTPRoute with request mirror filter",
Manifests: []string{"tests/httproute-request-mirror.yaml"},
Features: []suite.SupportedFeature{
suite.SupportGateway,
suite.SupportHTTPRoute,
suite.SupportHTTPRouteRequestMirror,
},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "request-mirror", 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: "infra-backend-v2",
Namespace: ns,
},
}
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, ns, tc.MirroredTo, tc.Request.Path)
})
}
},
}
24 changes: 24 additions & 0 deletions conformance/tests/httproute-request-mirror.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: request-mirror
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
backendRefs:
- name: infra-backend-v1
port: 8080
namespace: gateway-conformance-infra
3 changes: 3 additions & 0 deletions conformance/utils/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type ExpectedResponse struct {
Backend string
Namespace string

// MirroredTo is the destination pod of the mirrored request.
MirroredTo string

// User Given TestCase name
TestCaseName string
}
Expand Down
60 changes: 60 additions & 0 deletions conformance/utils/http/mirror.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
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 http

import (
"fmt"
"regexp"
"testing"
"time"

"github.com/stretchr/testify/require"
clientset "k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"

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

func ExpectMirroredRequest(t *testing.T, client client.Client, clientset *clientset.Clientset, ns, mirrorPod, path string) {
if mirrorPod == "" {
t.Fatalf("MirroredTo wasn't provided in the testcase, this test should only check http request mirror.")
}

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", 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 _, log := range logs {
if mirrorLogRegexp.MatchString(string(log)) {
mirrored = true
break
}
}
return mirrored

}, 60*time.Second, time.Second, "Mirrored request log wasn't found")

t.Log("Mirrored request log found")
}
64 changes: 64 additions & 0 deletions conformance/utils/kubernetes/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
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 kubernetes

import (
"context"
"io"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
clientset "k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// DumpEchoLogs returns logs of the echoserver pod in
// in the given namespace and with the given name.
func DumpEchoLogs(ns, name string, c client.Client, cs *clientset.Clientset) ([][]byte, error) {
var logs [][]byte

pods := new(corev1.PodList)
podListOptions := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(map[string]string{"app": name}),
Namespace: ns,
}
if err := c.List(context.TODO(), pods, podListOptions); err != nil {
return nil, err
}

podLogOptions := &corev1.PodLogOptions{
Container: name,
}
for _, pod := range pods.Items {
if pod.Status.Phase == corev1.PodFailed {
continue
}
req := cs.CoreV1().Pods(ns).GetLogs(pod.Name, podLogOptions)
logStream, err := req.Stream(context.TODO())
if err != nil {
continue
}
defer logStream.Close()
logBytes, err := io.ReadAll(logStream)
if err != nil {
continue
}
logs = append(logs, logBytes)
}

return logs, nil
}
4 changes: 4 additions & 0 deletions conformance/utils/suite/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ const (

// This option indicates support for HTTPRoute path rewrite (experimental conformance)
SupportHTTPRoutePathRewrite SupportedFeature = "HTTPRoutePathRewrite"

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

// HTTPExtendedFeatures includes all the supported features for HTTPRoute
Expand All @@ -151,6 +154,7 @@ var HTTPExtendedFeatures = sets.New(
SupportHTTPRoutePathRedirect,
SupportHTTPRouteHostRewrite,
SupportHTTPRoutePathRewrite,
SupportHTTPRouteRequestMirror,
).Insert(HTTPCoreFeatures.UnsortedList()...)

// -----------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions conformance/utils/suite/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"testing"

"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand All @@ -35,6 +36,7 @@ import (
// conformance tests.
type ConformanceTestSuite struct {
Client client.Client
Clientset *clientset.Clientset
RESTClient *rest.RESTClient
RestConfig *rest.Config
RoundTripper roundtripper.RoundTripper
Expand All @@ -54,6 +56,7 @@ type ConformanceTestSuite struct {
// Options can be used to initialize a ConformanceTestSuite.
type Options struct {
Client client.Client
Clientset *clientset.Clientset
RESTClient *rest.RESTClient
RestConfig *rest.Config
GatewayClassName string
Expand Down Expand Up @@ -113,6 +116,7 @@ func New(s Options) *ConformanceTestSuite {

suite := &ConformanceTestSuite{
Client: s.Client,
Clientset: s.Clientset,
RESTClient: s.RESTClient,
RestConfig: s.RestConfig,
RoundTripper: roundTripper,
Expand Down