Skip to content
Open
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
105 changes: 105 additions & 0 deletions conformance/tests/httproute-session-persistence-cookie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Copyright 2025 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 (
"fmt"
stdhttp "net/http"
"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"
"sigs.k8s.io/gateway-api/pkg/features"
)

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

var HTTPRouteSessionPersistenceCookie = suite.ConformanceTest{
ShortName: "HTTPRouteSessionPersistenceCookie",
Description: "An HTTPRoute with cookie-based session persistence routes requests with the same cookie to the same backend",
Manifests: []string{"tests/httproute-session-persistence-cookie.yaml"},
Features: []features.FeatureName{
features.SupportGateway,
features.SupportHTTPRoute,
features.SupportHTTPRouteSessionPersistenceCookie,
},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
const (
ns = "gateway-conformance-infra"
path = "/session-persistence"
)
routeNN := types.NamespacedName{Name: "session-persistence-cookie", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN)

expected := http.ExpectedResponse{
Request: http.Request{
Path: path,
},
Response: http.Response{
StatusCode: 200,
},
Namespace: ns,
}

http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expected)

req := http.MakeRequest(t, &expected, gwAddr, "HTTP", "http")
initialPod := ""
for i := 0; i < 10; i++ {
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The test uses a hardcoded value of 10 iterations to verify session persistence. Consider extracting this as a named constant at the package or function level (e.g., const numSessionPersistenceRequests = 10) to make it easier to understand the test's intent and allow for easier modification if needed.

Copilot uses AI. Check for mistakes.
cReq, cRes, err := suite.RoundTripper.CaptureRoundTrip(req)
if err != nil {
t.Fatalf("request %d with cookie failed: %v", i+1, err)
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The error message says "request %d with cookie failed" but on the first iteration (i=0), the cookie hasn't been set yet. The error message should be more accurate, perhaps "request %d failed" and only mention the cookie in subsequent iterations after i > 0.

Suggested change
t.Fatalf("request %d with cookie failed: %v", i+1, err)
if i == 0 {
t.Fatalf("request %d failed: %v", i+1, err)
} else {
t.Fatalf("request %d with cookie failed: %v", i+1, err)
}

Copilot uses AI. Check for mistakes.
}
if err := http.CompareRoundTrip(t, &req, cReq, cRes, expected); err != nil {
t.Fatalf("request %d with cookie failed expectations: %v", i+1, err)
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The error message says "request %d with cookie failed expectations" but on the first iteration (i=0), the cookie hasn't been set yet. The error message should be more accurate, perhaps "request %d failed expectations" and only mention the cookie in subsequent iterations after i > 0.

Suggested change
t.Fatalf("request %d with cookie failed expectations: %v", i+1, err)
if i == 0 {
t.Fatalf("request %d failed expectations: %v", i+1, err)
} else {
t.Fatalf("request %d with cookie failed expectations: %v", i+1, err)
}

Copilot uses AI. Check for mistakes.
}

if i == 0 {
if cReq.Pod == "" {
t.Fatalf("expected pod to be set")
}
cookie, err := parseCookie(cRes.Headers)
if err != nil {
t.Fatalf("failed to parse session persistence cookie: %v", err)
}
t.Logf("session persistence cookie: %s=%s", cookie.Name, cookie.Value)
req.Headers["Cookie"] = []string{fmt.Sprintf("%s=%s", cookie.Name, cookie.Value)}
initialPod = cReq.Pod
continue
}
if cReq.Pod != initialPod {
t.Fatalf("expected session persistence to keep routing to pod %q, got %q", initialPod, cReq.Pod)
}
}
},
}

func parseCookie(headers map[string][]string) (*stdhttp.Cookie, error) {
parser := &stdhttp.Response{Header: stdhttp.Header(headers)}
cookies := parser.Cookies()
if len(cookies) == 0 {
return nil, fmt.Errorf("cookie not found: headers: %v", headers)
}
return cookies[0], nil
Comment on lines +99 to +104
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The parseCookie function returns the first cookie from the response without verifying that it's the session persistence cookie. If the backend sets multiple cookies (e.g., tracking cookies, CSRF tokens, etc.), this function might return the wrong cookie. Consider either: 1) validating that the returned cookie has expected characteristics (e.g., specific name pattern), 2) iterating through cookies to find one that matches session persistence criteria, or 3) documenting the assumption that the first cookie is always the session persistence cookie.

Suggested change
parser := &stdhttp.Response{Header: stdhttp.Header(headers)}
cookies := parser.Cookies()
if len(cookies) == 0 {
return nil, fmt.Errorf("cookie not found: headers: %v", headers)
}
return cookies[0], nil
const sessionCookieName = "session-persistence"
parser := &stdhttp.Response{Header: stdhttp.Header(headers)}
cookies := parser.Cookies()
if len(cookies) == 0 {
return nil, fmt.Errorf("cookie not found: headers: %v", headers)
}
for _, cookie := range cookies {
if cookie.Name == sessionCookieName {
return cookie, nil
}
}
return nil, fmt.Errorf("session persistence cookie %q not found: headers: %v", sessionCookieName, headers)

Copilot uses AI. Check for mistakes.
}
22 changes: 22 additions & 0 deletions conformance/tests/httproute-session-persistence-cookie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: session-persistence-cookie
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- matches:
- path:
type: PathPrefix
value: /session-persistence
sessionPersistence:
type: Cookie
cookieConfig:
lifetimeType: Session
backendRefs:
- name: infra-backend-v1
port: 8080
- name: infra-backend-v2
port: 8080
10 changes: 6 additions & 4 deletions hack/implementations/envoy-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ EOF
## Step 4: Run a specific conformance test

```shell script
go test -v ./conformance \
--run TestConformance/TLSRouteSimpleSameNamespace \
--gateway-class=envoy-gateway --supported-features=Gateway,TLSRoute \
--allow-crds-mismatch
go test -v ../../../conformance \
--run TestConformance/HTTPRouteSessionPersistenceCookie \
--gateway-class=envoy-gateway \
--supported-features=Gateway,HTTPRoute,HTTPRouteSessionPersistenceCookie \
--allow-crds-mismatch \
--cleanup-base-resources=false --debug
```

## (Optional) Step 4: Delete kind cluster
Expand Down
9 changes: 9 additions & 0 deletions pkg/features/httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ const (

// This option indicates support for HTTPRoute additional redirect status code 303 (extended conformance)
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

The comment for SupportHTTPRoute308RedirectStatusCode incorrectly states "redirect status code 303" when it should state "redirect status code 308". This appears to be a copy-paste error from line 107.

Suggested change
// This option indicates support for HTTPRoute additional redirect status code 303 (extended conformance)
// This option indicates support for HTTPRoute additional redirect status code 308 (extended conformance)

Copilot uses AI. Check for mistakes.
SupportHTTPRoute308RedirectStatusCode FeatureName = "HTTPRoute308RedirectStatusCode"

// This option indicates support for HTTPRoute cookie-based session persistence (extended conformance).
SupportHTTPRouteSessionPersistenceCookie FeatureName = "HTTPRouteSessionPersistenceCookie"
)

var (
Expand Down Expand Up @@ -229,6 +232,11 @@ var (
Name: SupportHTTPRoute308RedirectStatusCode,
Channel: FeatureChannelStandard,
}
// HTTPRouteSessionPersistenceCookieFeature contains metadata for the SupportHTTPRouteSessionPersistenceCookie feature.
HTTPRouteSessionPersistenceCookieFeature = Feature{
Name: SupportHTTPRouteSessionPersistenceCookie,
Channel: FeatureChannelExperimental,
}
)

// HTTPRouteExtendedFeatures includes all extended features for HTTPRoute
Expand Down Expand Up @@ -258,4 +266,5 @@ var HTTPRouteExtendedFeatures = sets.New(
HTTPRoute303RedirectStatusCodeFeature,
HTTPRoute307RedirectStatusCodeFeature,
HTTPRoute308RedirectStatusCodeFeature,
HTTPRouteSessionPersistenceCookieFeature,
)
Loading