From 190610d52642e383ad1af00d938a47546f597f84 Mon Sep 17 00:00:00 2001 From: liuhy Date: Wed, 24 Jun 2026 11:14:32 +0800 Subject: [PATCH 1/4] fix: register ListenerSet and route indexes in offline controller The offline controller used by the File provider was missing several field indexes that the Gateway reconciliation logic depends on. This caused repeated errors during reconciliation: 'failed to list ListenerSets: no index with name gatewayListenerSetIndex has been registered' The following indexes were missing: - gatewayListenerSetIndex on ListenerSet (root cause of the error) - listenerSetHTTPRouteIndex on HTTPRoute - listenerSetGRPCRouteIndex on GRPCRoute - listenerSetTCPRouteIndex on TCPRoute - listenerSetUDPRouteIndex on UDPRoute - listenerSetTLSRouteIndex on TLSRoute - clusterTrustBundleEepIndex on EnvoyExtensionPolicy These indexes are registered by the Kubernetes provider but were not carried over when the offline controller was created for standalone mode. Register them in newOfflineGatewayAPIClient to match the K8s provider setup, and add corresponding test coverage. Fixes: Standalone mode ListenerSet reconciliation errors Signed-off-by: liuhy --- .../provider/kubernetes/controller_offline.go | 7 +++++++ .../kubernetes/controller_offline_test.go | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/internal/provider/kubernetes/controller_offline.go b/internal/provider/kubernetes/controller_offline.go index f3bcdf15aa..bd13320c93 100644 --- a/internal/provider/kubernetes/controller_offline.go +++ b/internal/provider/kubernetes/controller_offline.go @@ -159,17 +159,23 @@ func newOfflineGatewayAPIClient(extensionPolicies []schema.GroupVersionKind) cli WithScheme(scheme). WithIndex(&gwapiv1.Gateway{}, classGatewayIndex, gatewayIndexFunc). WithIndex(&gwapiv1.Gateway{}, secretGatewayIndex, secretGatewayIndexFunc). + WithIndex(&gwapiv1.ListenerSet{}, gatewayListenerSetIndex, gatewayListenerSetIndexFunc). WithIndex(&gwapiv1.HTTPRoute{}, gatewayHTTPRouteIndex, gatewayHTTPRouteIndexFunc). WithIndex(&gwapiv1.HTTPRoute{}, backendHTTPRouteIndex, backendHTTPRouteIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, listenerSetHTTPRouteIndex, listenerSetHTTPRouteIndexFunc). WithIndex(&gwapiv1.HTTPRoute{}, httpRouteFilterHTTPRouteIndex, httpRouteFilterHTTPRouteIndexFunc). WithIndex(&gwapiv1.GRPCRoute{}, gatewayGRPCRouteIndex, gatewayGRPCRouteIndexFunc). WithIndex(&gwapiv1.GRPCRoute{}, backendGRPCRouteIndex, backendGRPCRouteIndexFunc). + WithIndex(&gwapiv1.GRPCRoute{}, listenerSetGRPCRouteIndex, listenerSetGRPCRouteIndexFunc). WithIndex(&gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, gatewayTCPRouteIndexFunc). WithIndex(&gwapiv1a2.TCPRoute{}, backendTCPRouteIndex, backendTCPRouteIndexFunc). + WithIndex(&gwapiv1a2.TCPRoute{}, listenerSetTCPRouteIndex, listenerSetTCPRouteIndexFunc). WithIndex(&gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, gatewayUDPRouteIndexFunc). WithIndex(&gwapiv1a2.UDPRoute{}, backendUDPRouteIndex, backendUDPRouteIndexFunc). + WithIndex(&gwapiv1a2.UDPRoute{}, listenerSetUDPRouteIndex, listenerSetUDPRouteIndexFunc). WithIndex(&gwapiv1.TLSRoute{}, gatewayTLSRouteIndex, gatewayTLSRouteIndexFunc). WithIndex(&gwapiv1.TLSRoute{}, backendTLSRouteIndex, backendTLSRouteIndexFunc). + WithIndex(&gwapiv1.TLSRoute{}, listenerSetTLSRouteIndex, listenerSetTLSRouteIndexFunc). WithIndex(&egv1a1.EnvoyProxy{}, backendEnvoyProxyTelemetryIndex, backendEnvoyProxyTelemetryIndexFunc). WithIndex(&egv1a1.EnvoyProxy{}, secretEnvoyProxyIndex, secretEnvoyProxyIndexFunc). WithIndex(&egv1a1.BackendTrafficPolicy{}, configMapBtpIndex, configMapBtpIndexFunc). @@ -182,6 +188,7 @@ func newOfflineGatewayAPIClient(extensionPolicies []schema.GroupVersionKind) cli WithIndex(&egv1a1.EnvoyExtensionPolicy{}, backendEnvoyExtensionPolicyIndex, backendEnvoyExtensionPolicyIndexFunc). WithIndex(&egv1a1.EnvoyExtensionPolicy{}, secretEnvoyExtensionPolicyIndex, secretEnvoyExtensionPolicyIndexFunc). WithIndex(&egv1a1.EnvoyExtensionPolicy{}, configMapEepIndex, configMapEepIndexFunc). + WithIndex(&egv1a1.EnvoyExtensionPolicy{}, clusterTrustBundleEepIndex, clusterTrustBundleEepIndexFunc). WithIndex(&gwapiv1.BackendTLSPolicy{}, configMapBtlsIndex, configMapBtlsIndexFunc). WithIndex(&gwapiv1.BackendTLSPolicy{}, secretBtlsIndex, secretBtlsIndexFunc). WithIndex(&gwapiv1.BackendTLSPolicy{}, clusterTrustBundleBtlsIndex, clusterTrustBundleBtlsIndexFunc). diff --git a/internal/provider/kubernetes/controller_offline_test.go b/internal/provider/kubernetes/controller_offline_test.go index f64a29ce39..8f6198890a 100644 --- a/internal/provider/kubernetes/controller_offline_test.go +++ b/internal/provider/kubernetes/controller_offline_test.go @@ -141,11 +141,18 @@ func TestNewOfflineGatewayAPIControllerIndexRegistration(t *testing.T) { require.NoError(t, err) }) + t.Run("ListenerSet index", func(t *testing.T) { + err := cli.List(context.Background(), &gwapiv1.ListenerSetList{}, client.MatchingFields{gatewayListenerSetIndex: "any"}) + require.NoError(t, err) + }) + t.Run("HTTPRoute indices", func(t *testing.T) { err := cli.List(context.Background(), &gwapiv1.HTTPRouteList{}, client.MatchingFields{gatewayHTTPRouteIndex: "any"}) require.NoError(t, err) err = cli.List(context.Background(), &gwapiv1.HTTPRouteList{}, client.MatchingFields{backendHTTPRouteIndex: "any"}) require.NoError(t, err) + err = cli.List(context.Background(), &gwapiv1.HTTPRouteList{}, client.MatchingFields{listenerSetHTTPRouteIndex: "any"}) + require.NoError(t, err) err = cli.List(context.Background(), &gwapiv1.HTTPRouteList{}, client.MatchingFields{httpRouteFilterHTTPRouteIndex: "any"}) require.NoError(t, err) }) @@ -155,6 +162,8 @@ func TestNewOfflineGatewayAPIControllerIndexRegistration(t *testing.T) { require.NoError(t, err) err = cli.List(context.Background(), &gwapiv1.GRPCRouteList{}, client.MatchingFields{backendGRPCRouteIndex: "any"}) require.NoError(t, err) + err = cli.List(context.Background(), &gwapiv1.GRPCRouteList{}, client.MatchingFields{listenerSetGRPCRouteIndex: "any"}) + require.NoError(t, err) }) t.Run("TCPRoute indices", func(t *testing.T) { @@ -162,6 +171,8 @@ func TestNewOfflineGatewayAPIControllerIndexRegistration(t *testing.T) { require.NoError(t, err) err = cli.List(context.Background(), &gwapiv1a2.TCPRouteList{}, client.MatchingFields{backendTCPRouteIndex: "any"}) require.NoError(t, err) + err = cli.List(context.Background(), &gwapiv1a2.TCPRouteList{}, client.MatchingFields{listenerSetTCPRouteIndex: "any"}) + require.NoError(t, err) }) t.Run("UDPRoute indices", func(t *testing.T) { @@ -169,6 +180,8 @@ func TestNewOfflineGatewayAPIControllerIndexRegistration(t *testing.T) { require.NoError(t, err) err = cli.List(context.Background(), &gwapiv1a2.UDPRouteList{}, client.MatchingFields{backendUDPRouteIndex: "any"}) require.NoError(t, err) + err = cli.List(context.Background(), &gwapiv1a2.UDPRouteList{}, client.MatchingFields{listenerSetUDPRouteIndex: "any"}) + require.NoError(t, err) }) t.Run("TLSRoute indices", func(t *testing.T) { @@ -176,6 +189,8 @@ func TestNewOfflineGatewayAPIControllerIndexRegistration(t *testing.T) { require.NoError(t, err) err = cli.List(context.Background(), &gwapiv1.TLSRouteList{}, client.MatchingFields{backendTLSRouteIndex: "any"}) require.NoError(t, err) + err = cli.List(context.Background(), &gwapiv1.TLSRouteList{}, client.MatchingFields{listenerSetTLSRouteIndex: "any"}) + require.NoError(t, err) }) t.Run("EnvoyProxy indices", func(t *testing.T) { @@ -215,6 +230,8 @@ func TestNewOfflineGatewayAPIControllerIndexRegistration(t *testing.T) { require.NoError(t, err) err = cli.List(context.Background(), &egv1a1.EnvoyExtensionPolicyList{}, client.MatchingFields{configMapEepIndex: "any"}) require.NoError(t, err) + err = cli.List(context.Background(), &egv1a1.EnvoyExtensionPolicyList{}, client.MatchingFields{clusterTrustBundleEepIndex: "any"}) + require.NoError(t, err) }) t.Run("BackendTLSPolicy indices", func(t *testing.T) { From 5a1e31a3e5aa88160f42af68eaf5575ca6c2b695 Mon Sep 17 00:00:00 2001 From: liuhy Date: Wed, 24 Jun 2026 15:22:54 +0800 Subject: [PATCH 2/4] Document the standalone offline controller fix for release consumers PR #9320 already restores the missing offline controller indexes, but the follow-up review correctly called out that the user-visible fix still needs a release note fragment under the repository's new per-change changelog layout. Constraint: The repository no longer uses release-notes/current.yaml; release notes must be added as current/
/-.md fragments. Rejected: Editing release-notes/current.yaml | the file has been replaced on the PR branch by fragment-based release notes. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep follow-up-only PR fixes constrained to the missing release-note fragment unless a new reviewer finding requires code changes. Tested: bash tools/hack/check-release-notes-filenames.sh; go test ./internal/provider/kubernetes -run TestNewOfflineGatewayAPIControllerIndexRegistration -count=1 Not-tested: Full repository test suite Signed-off-by: liuhy --- .../bug_fixes/9320-offline-controller-listenerset-indexes.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 release-notes/current/bug_fixes/9320-offline-controller-listenerset-indexes.md diff --git a/release-notes/current/bug_fixes/9320-offline-controller-listenerset-indexes.md b/release-notes/current/bug_fixes/9320-offline-controller-listenerset-indexes.md new file mode 100644 index 0000000000..da8d964506 --- /dev/null +++ b/release-notes/current/bug_fixes/9320-offline-controller-listenerset-indexes.md @@ -0,0 +1 @@ +Fixed the standalone offline controller used by the File provider to register the missing ListenerSet, ListenerSet-owned route, and ClusterTrustBundle EnvoyExtensionPolicy indexes so reconciliation no longer fails with unregistered index errors. From 1426f4f783dcfcd9ef51af76c52d8a2d1a8b63ce Mon Sep 17 00:00:00 2001 From: liuhy Date: Wed, 24 Jun 2026 17:10:15 +0800 Subject: [PATCH 3/4] Avoid stale DNS cache collisions in UDP FQDN e2e coverage The dual-stack UDPRouteBackendFQDNTest reuses the same coredns service hostname that the preceding UDPRoute test deletes and recreates. In CI this can leave Envoy resolving a stale service address from its DNS cache, causing the UDP FQDN backend check to time out for the full poll window even though the route and backend are accepted. Use a dedicated service hostname for the FQDN-specific UDPRoute manifest so the test exercises the intended backend path without inheriting cached DNS state from the previous test. Constraint: The fix must stay within PR #9320 scope and avoid unrelated product-code changes. Rejected: Add sleeps or longer polling to the test | hides the stale-hostname issue without removing the cache collision. Confidence: medium Scope-risk: narrow Reversibility: clean Directive: Keep UDP FQDN backend test hostnames isolated from other UDP backend fixtures when tests create and recreate services in the same namespace. Tested: go test ./e2e/... -run '^$' -tags=e2e (from /test) Not-tested: Full e2e matrix, including v1.35.0 dual-stack runtime execution Signed-off-by: liuhy --- test/e2e/testdata/udproute-to-backend-fqdn.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/testdata/udproute-to-backend-fqdn.yaml b/test/e2e/testdata/udproute-to-backend-fqdn.yaml index 8db8e12341..011ab5722d 100644 --- a/test/e2e/testdata/udproute-to-backend-fqdn.yaml +++ b/test/e2e/testdata/udproute-to-backend-fqdn.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: coredns + name: coredns-fqdn namespace: gateway-conformance-infra labels: app: udp @@ -101,5 +101,5 @@ metadata: spec: endpoints: - fqdn: - hostname: coredns.gateway-conformance-infra.svc.cluster.local + hostname: coredns-fqdn.gateway-conformance-infra.svc.cluster.local port: 53 From a8065ef20a97a785e9c59b5ee3ccf0d3a4e09655 Mon Sep 17 00:00:00 2001 From: liuhy Date: Wed, 24 Jun 2026 17:12:04 +0800 Subject: [PATCH 4/4] Revert "Avoid stale DNS cache collisions in UDP FQDN e2e coverage" This reverts commit 1426f4f783dcfcd9ef51af76c52d8a2d1a8b63ce. Signed-off-by: liuhy --- test/e2e/testdata/udproute-to-backend-fqdn.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/testdata/udproute-to-backend-fqdn.yaml b/test/e2e/testdata/udproute-to-backend-fqdn.yaml index 011ab5722d..8db8e12341 100644 --- a/test/e2e/testdata/udproute-to-backend-fqdn.yaml +++ b/test/e2e/testdata/udproute-to-backend-fqdn.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: coredns-fqdn + name: coredns namespace: gateway-conformance-infra labels: app: udp @@ -101,5 +101,5 @@ metadata: spec: endpoints: - fqdn: - hostname: coredns-fqdn.gateway-conformance-infra.svc.cluster.local + hostname: coredns.gateway-conformance-infra.svc.cluster.local port: 53