Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions .github/actions/kubernetes-e2e-tests/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ inputs:
matrix-label:
required: true
description: The version of the matrix being used - used to name artifacts to prevent filename collisions
gosum-test-args:
required: false
description: The arguments passed to the gosum test invocation
default: '--format=testname'

runs:
using: "composite"
Expand All @@ -29,6 +33,7 @@ runs:
GO_TEST_USER_ARGS: ${{ inputs.test-args }} -run "${{ inputs.run-regex }}" -v
CLUSTER_NAME: ${{ inputs.cluster-name }}
ISTIO_VERSION: ${{ inputs.istio-version }}
GOTESTSUM_ARGS: ${{ inputs.gosum-test-args }}
shell: bash
run: make e2e-test
- name: Archive bug report directory on failure
Expand Down
10 changes: 8 additions & 2 deletions .github/actions/setup-kind-cluster/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ inputs:
istio-version:
required: true
description: The version of Istio
gateway-api-version:
kgateway-api-version:
required: false
default: "" # If this is undefined the setup-kind script has its own default value
description: The version of the gateway-api to use
kgateway-api-channel:
required: false
default: "" # If this is undefined the setup-kind script has its own default value
description: The channel of the gateway-api to use
localstack:
required: false
default: "false"
Expand All @@ -37,8 +41,10 @@ runs:
CLUSTER_NAME: ${{ inputs.cluster-name }}
CLUSTER_NODE_VERSION: ${{ inputs.kind-node-version }}
ISTIO_VERSION: ${{ inputs.istio-version }}
CONFORMANCE_VERSION: ${{ inputs.gateway-api-version }}
CONFORMANCE_VERSION: ${{ inputs.kgateway-api-version }}
CONFORMANCE_CHANNEL: ${{ inputs.kgateway-api-channel }}
LOCALSTACK: ${{ inputs.localstack }}
# AI Tests rely on metal LB
CONFORMANCE: true
run: |
./hack/kind/setup-kind.sh
1 change: 0 additions & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ jobs:
- cluster-name: 'agent-gateway-cluster'
go-test-args: '-v -timeout=25m'
go-test-run-regex: '^TestAgentgatewayIntegration'
agentgateway: 'true'
# August 29, 2025: ~3 minutes
- cluster-name: 'api-validation'
go-test-args: '-v -timeout=10m'
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/nightly-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ on:
description: "Run load testing suite"
type: boolean
default: false
run-e2e-tests:
description: "Run e2e tests"
type: boolean
default: false

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
Expand Down Expand Up @@ -59,3 +63,53 @@ jobs:
with:
ref: main
- uses: ./.github/actions/kube-gateway-api-load-tests

kgateway_e2e_tests_for_gateway_api_versions:
name: E2E Tests for Gateway API Versions
if: ${{ (github.event_name == 'workflow_dispatch' && inputs.run-e2e-tests)}}
runs-on: ubuntu-22.04
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
gateway-api-version: [ { version: 'v1.4.0', channel: 'experimental' },
{ version: 'v1.4.0', channel: 'standard' },
{ version: 'v1.3.0', channel: 'experimental' },
{ version: 'v1.3.0', channel: 'standard' },
{ version: 'v1.2.1', channel: 'experimental' },
{ version: 'v1.2.1', channel: 'standard' }]
test-regex: [ {name: "kgateway", regex: "^Test[^A]"},
{name: "agentgateway", regex: "^TestA" } ]
steps:
- uses: actions/checkout@v4
- name: Prep Go Runner
uses: ./.github/actions/prep-go-runner
- name: Dotenv Action
uses: falti/[email protected]
id: dotenv
with:
path: "./.github/workflows/.env/nightly-tests/max_versions.env"
log-variables: true
- name: Setup KinD Cluster
uses: ./.github/actions/setup-kind-cluster
with:
kgateway-api-version: ${{ matrix.gateway-api-version.version }}
kgateway-api-channel: ${{ matrix.gateway-api-version.channel }}
cluster-name: "kgw-api-e2e-${{ matrix.gateway-api-version.version }}-${{ matrix.gateway-api-version.channel }}-${{ matrix.test-regex.name }}"
kubectl-version: ${{ steps.dotenv.outputs.kubectl_version }}
istio-version: ${{ steps.dotenv.outputs.istio_version }}
kind-node-version: ${{ steps.dotenv.outputs.node_version }}
- id: run-tests
uses: ./.github/actions/kubernetes-e2e-tests
env:
VERSION: '1.0.0-ci1'
GITHUB_TOKEN: ${{ github.token }}
GO_TEST_RETRIES: '2'
with:
cluster-name: "kgw-api-e2e-${{ matrix.gateway-api-version.version }}-${{ matrix.gateway-api-version.channel }}-${{ matrix.test-regex.name }}"
test-args: '-v -timeout=120m'
run-regex: ${{ matrix.test-regex.regex }}
istio-version: ${{ steps.dotenv.outputs.istio_version }}
matrix-label: "nightly-kgw-api-${{ matrix.gateway-api-version.version }}-${{ matrix.gateway-api-version.channel }}-${{ matrix.test-regex.name }}"
gosum-test-args: '--format=standard-verbose'

145 changes: 145 additions & 0 deletions design/12721.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# EP-12721: E2e testing with Gateway API Versions


* Issue: [12721](https://github.com/kgateway-dev/kgateway/issues/12721)


## Background
The current e2e tests assume the latest supported experimental version of the `gateway.networking.k8s.io` APIs is installed. This will not always be the case in the environments in which kgateway is deployed. In order to validate functionality across a wider range of environments, we will allow testing with different versions of the Gateway API.

In addition to different semver designated versions of the API, there are two channels, `standard` and `experimental`


### Differences in API versions
* v0.3.0
* TCPRoute, TLSRoute, and the unused UDPRoute added to experimental (not available in standard as of v1.4.0)
* v1.1.0
* SessionPersistance for HTTPRoute rules added to experimental (not available in standard as of v1.4.0)
* v1.2.0
* HTTPRoutes.spec.rules[].name added in experimental (promoted to standard in v1.4.0)
* v1.3.0
* XListenerSets added to experimental (not available in standard as of v1.4.0, planned for v1.5.0)
* CORS filters added to experimental (not available in standard as of v1.4.0)
* v1.4.0
* BackendTLSPolicy promoted to v1 in standard and experimental. Previous v1alpha3 version is not supported.
* HTTPRoutes.spec.rules[].name added to standard

The are a substantial number of tests that need to be modified to


## Motivation
Better test coverage and understanding of how kgateway works with different Gateway API versions

## Goals
* E2E tests can be run locally or in CI with different versions (semver and channel) of the Gateway API
* Consistent approach to managing resources for different versions of the API

## Non-Goals
* Mass update of existing tests to use the BaseTestingSuite
* Suites that don't use the BaseTestingSuite will continue to run all tests for any GatewayAPI version
* Tests that need implement version dependent behavior will be migrated to BaseTestingSuite as needed
* Running tests in CRC/Openshift
* Updating application code to support earlier versions


## Implementation Details
### Determining the Gateway API version
The Gateway API CRDs contain two relevant annotations:
* `gateway.networking.k8s.io/bundle-version` - the API version, for example `gateway.networking.k8s.io/bundle-version: v1.2.0`
* `gateway.networking.k8s.io/channel` - the API channel, standard or experimental, for example `gateway.networking.k8s.io/channel: standard`

These annotations can be examined to determine the version. If the annotations are not present, this should be considered a fatal error.

### Test cases
The e2e tests are built up of [TestCases](https://github.com/kgateway-dev/kgateway/blob/2b04f3d1465257d0c449687922ea6e92603b822c/test/kubernetes/e2e/tests/base/base_suite.go#L33) that define the resources used for the tests.

In order to allow test cases to run conditionally based on the API version, we will add new fields, `MinGwApiVersion` and `MaxGwApiVersion` to the TestCase struct:

```
// MinGwApiVersion specifies the minimum Gateway API version required per channel.
// Map key is the channel (GwApiChannelStandard or GwApiChannelExperimental), value is the minimum version.
// If the map is empty/nil, the test runs on any channel/version.
// The test will only run if the Gateway API version is >= the specified minimum version.
// For minimum requirements, if only experimental constraints exist, the test is considered experimental-only and will skip on standard channel.
// Matching logic based on installed channel:
// - experimental: If experimental key exists, check version; otherwise run
// - standard: If standard key exists, check version; if only experimental exists, skip; otherwise runs on any standard version.
MinGwApiVersion map[GwApiChannel]*GwApiVersion

// MaxGwApiVersion specifies the maximum Gateway API version required per channel.
// Map key is the channel (GwApiChannelStandard or GwApiChannelExperimental), value is the maximum version.
// If the map is empty/nil, the test runs on any channel/version.
// The test will only run if the Gateway API version is < the specified maximum version.
// Maximum constraints are channel-specific - experimental constraints don't affect standard channel execution.
// If the maximum version is less than the minimum version, the test will be skipped.
MaxGwApiVersion map[GwApiChannel]*GwApiVersion
```

`MinGwApiChannel` is a typed string with the value of `experimental` or `standard`, and will define the minimum version of the API needed to run the test for the channel. If the current installation is now greater or equal to the required version, the test will be skipped. If no `MinGwApiChannel` value is defined, the test will run on any version of the API. The exception to this logic is if the standard channel is installed and the `MinGwApiVersion` for the TestCase only defines an experimental minimum version, for example:

```
MinGwApiVersion: map[base.GwApiChannel]*semver.Version{
base.GwApiChannelExperimental: base.GwApiV1_3_0,
},
```

This will be interpreted as "the test needs to use features available in experimental API v1.4.0; these features are not yet available in the standard channel". In this case, the test will be skipped for all standard channel versions.

`GwApiVersion` is a wrapper around the underlying semver packages used, and was created in order to allow test suites to use semver types without having to know about the underlying implementation.


### Test Suites


#### SetupByVersion
A common pattern used in our e2e tests is to setup a Gateway and possibly other resources during suite setup and using them for every test. This pattern allows the tests to run faster, as time is not spent deploying and removing Gateways. In the [BaseTestingSuite](https://github.com/kgateway-dev/kgateway/blob/2b04f3d1465257d0c449687922ea6e92603b822c/test/kubernetes/e2e/tests/base/base_suite.go#L49C1-L66C2), these resources are defined by the [Setup](https://github.com/kgateway-dev/kgateway/blob/2b04f3d1465257d0c449687922ea6e92603b822c/test/kubernetes/e2e/tests/base/base_suite.go#L53) field

However, once we allow tests to run for different versions of the API, we are no longer in a "one configuration fits all" situation. For example, using ListenerSets requires `allowedListeners` to be defined on the Gateway, but this field will cause the resource to be rejected when using older versions of the API.

To accommodate this, we will add a new field `SetupByVersion` to the BaseTestingSuite:
```
// SetupByVersion allows defining different setup configurations for different GW API versions and channels.
// The outer map key is the channel (standard or experimental).
// The inner map key is the minimum version, and the value is the TestCase to use.
// The system will select the setup with the highest matching version for the current channel.
// If no setups match, falls back to the Setup field for backward compatibility.
// Example:
// SetupByVersion: map[GwApiChannel]map[*semver.Version]*TestCase{
// GwApiChannelExperimental: {
// GwApiV1_3_0: &setupExperimentalV1_3,
// },
// GwApiChannelStandard: {
// GwApiV1_3_0: &setupStandardV1_4,
// },
// }
SetupByVersion map[GwApiChannel]map[*semver.Version]*TestCase
```

When choosing which setup to use, the suite will use the highest defined semver for the channel that is less than or equal to the current version, falling back to the existing `Setup` if there is no such version.

There are other data structures that could be used to store the setup information, but this approach was chosen because by making channel and version keys for the map, we guarantee that it will be unambiguous which setup to use.

#### MinGwApiVersion

`MinGwApiVersion` has also been added at the suite level to allow entire suites to be skipped.

This is used for the cases where all the tests in a suite require configuration not available in all Gw API versions, and it was introduced because test suites apply their setup before running (or skipping) the individual test cases. In these cases, the suite may run its setup with resources incompatible with the installed version of the Gw API, and we would not want to restore those resources.

### DevX
* This approach requires no changes for tests and suites that aren't version sensitive
* If a test needs to be skipped on certain versions, it can configured on the test case
* If a suite requires different setups/gateways based on version, once the setup is configured additional test cases just need to be congfigured with the versions they run on.


### Test Plan
Successful runs of a GitHub job across versions v1.2-1.4 in both channels.

Tests and suites will be adapted to older versions in 2 ways:
* If a test requires a feature (like XListenerSets or rule names in HTTPRoutes), those tests will be skipped.
* Some tests will fail because the suite setup or test resources have invalid config for a Gw API version, but the test itself does not. For example, a Gateway for the suite may be configured with `allowedListeners`, but only some tests use listenersets. In this case we will split the resources and use a combination of SetupForVersion and MinGwApiVersion to apply the appropriate config and run the appropriate tests for the Gw API version.

## Alternatives
Do not test other versions of the API.

## Open Questions
* Should we be able to set minimum version at the suite level? EG, for the listenerset suite, when we know that no tests in the suite will run?
4 changes: 4 additions & 0 deletions devel/testing/nightly-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ The following are run on a schedule via a [GitHub action](/.github/workflows/nig
## Gateway API conformance tests
Kubernetes Gateway API conformance tests are run using the earliest and latest supported k8s versions.

## Gateway Load Tests
Kubernetes Gateway load tests are run using the earliest and latest supported k8s versions.

## E2E tests with different Gateway API versions
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ require (
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
Expand Down
1 change: 1 addition & 0 deletions hack/utils/oss_compliance/osa_provided.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Name|Version|License
---|---|---
[cel.dev/expr](https://cel.dev/expr)|v0.24.0|Apache License 2.0
[semver/v3](https://github.com/Masterminds/semver)|v3.4.0|MIT License
[agentgateway/agentgateway](https://github.com/agentgateway/agentgateway)|v0.10.3|Apache License 2.0
[anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go)|v1.13.0|MIT License
[retry-go/v4](https://github.com/avast/retry-go)|v4.3.3|MIT License
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ func (tc TestCase) Run(
}

for _, file := range tc.InputFiles {
objs, err := testutils.LoadFromFiles(file, scheme, gvkToStructuralSchema)
objs, err := testutils.LoadFromFiles(file, scheme, gvkToStructuralSchema, "")
if err != nil {
return ActualTestResult{}, err
}
Expand Down
17 changes: 17 additions & 0 deletions pkg/utils/kubeutils/kubectl/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,20 @@ func (c *Cli) GetLeaseHolder(ctx context.Context, namespace string, leaderElecti
stdout = strings.Trim(stdout, "'")
return stdout + stderr, err
}

// GetDefaultNamespace returns the default namespace configured in the current kubectl context.
// If no namespace is configured, it returns "default".
func (c *Cli) GetDefaultNamespace(ctx context.Context) (string, error) {
stdout, _, err := c.Execute(ctx, "config", "view", "--minify", "--output", "jsonpath={..namespace}")
if err != nil {
return "", err
}

namespace := strings.TrimSpace(stdout)
if namespace == "" {
// If no namespace is configured in context, kubectl uses "default"
return "default", nil
}

return namespace, nil
}
2 changes: 1 addition & 1 deletion test/deployer/deployer_helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (dt DeployerTester) RunHelmChartTest(
gvkToStructuralSchema, err := testutils.GetStructuralSchemas(crdDir)
assert.NoError(t, err, "error getting structural schemas")

objs, err := testutils.LoadFromFiles(inputFile, scheme, gvkToStructuralSchema)
objs, err := testutils.LoadFromFiles(inputFile, scheme, gvkToStructuralSchema, "")
assert.NoError(t, err, "error loading files from input file")

commonObjs, gtw := ExtractCommonObjs(t, objs)
Expand Down
8 changes: 1 addition & 7 deletions test/e2e/features/agentgateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@

## Setup

To use the agentgateway control plane with kgateway, you need to enable the integration in the helm chart via
`--set agentgateway.enabled=true` or in the values.yaml:
```yaml
agentgateway:
enabled: true # set this to true
```
The agentgateway control plane is automatically enabled when installing kgateway.

This is done automatically by the agentgateway e2e suite.

## Testing with an unreleased agentgateway commit

Expand Down
5 changes: 4 additions & 1 deletion test/e2e/features/agentgateway/aibackend/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ type testingSuite struct {
}

func NewTestingSuite(ctx context.Context, testInst *e2e.TestInstallation) suite.TestingSuite {
// This suite applies TrafficPolicy to specific named sections of the HTTPRoute, and requires HTTPRoutes.spec.rules[].name to be present in the Gateway API version.
return &testingSuite{
base.NewBaseTestingSuite(ctx, testInst, setup, testCases),
BaseTestingSuite: base.NewBaseTestingSuite(ctx, testInst, setup, testCases,
base.WithMinGwApiVersion(base.GwApiRequireRouteNames),
),
}
}

Expand Down
1 change: 0 additions & 1 deletion test/e2e/features/agentgateway/rbac/testdata/cel-rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ spec:
name: httpbin
port: 8000
weight: 1
name: httpbin-get
matches:
- path:
type: PathPrefix
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/features/agentgateway/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type testingSuite struct {
}

func NewTestingSuite(ctx context.Context, testInst *e2e.TestInstallation) suite.TestingSuite {
// This suite applies TrafficPolicy to specific named sections of the HTTPRoute, and requires HTTPRoutes.spec.rules[].name to be present in the Gateway API version.
return &testingSuite{
base.NewBaseTestingSuite(ctx, testInst, base.TestCase{}, testCases),
BaseTestingSuite: base.NewBaseTestingSuite(ctx, testInst, base.TestCase{}, testCases, base.WithMinGwApiVersion(base.GwApiRequireRouteNames)),
}
}

Expand Down
3 changes: 3 additions & 0 deletions test/e2e/features/agentgateway/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ var (
},
"TestAgentgatewayTCPRoute": {
Manifests: []string{defaults.CurlPodManifest, tcpRouteManifest},
MinGwApiVersion: map[base.GwApiChannel]*base.GwApiVersion{
base.GwApiChannelExperimental: &base.GwApiAny, // TCPRoutes are experimental only
},
},
}
)
Loading
Loading