Repository: envoyproxy/gateway

Issue:
the controller read the customized service name from EnvoyProxy failed
Set the customized service name in  EnvoyProxy, and the EnvoyProxy has attached to GatewayClass

<img width="903" height="189" alt="Image" src="https://github.com/user-attachments/assets/2edf9ed8-0f0a-4cfb-adce-4e34d8dc7dfd" />

But the controller reports the auto-generated service name

<img width="1960" height="183" alt="Image" src="https://github.com/user-attachments/assets/40ee7ca4-7c92-4fed-a14e-9218bcdfd523" />

Hints from issue comments:
@qicz: IMO the follows does not work

```golang
mergedGateways := false
	if r.mergeGateways.Has(managedGC.Name) {
		mergedGateways = true
		// processGatewayClassParamsRef has been called for this gatewayclass, its EnvoyProxy should exist in resourceTree
		r.processServiceClusterForGatewayClass(resourceTree.EnvoyProxyForGatewayClass, managedGC, resourceMap)
	}
```

and we should check the ep is not nil 

```golang

		if !mergedGateways {
			var ep *egv1a1.EnvoyProxy = nil
			if gtw.Spec.Infrastructure != nil && gtw.Spec.Infrastructure.ParametersRef != nil {
				// processGatewayParamsRef has been called for this gateway, its EnvoyProxy should exist in resourceTree
				ep = resourceTree.GetEnvoyProxy(gtw.Namespace, gtw.Spec.Infrastructure.ParametersRef.Name)
			}
			r.processServiceClusterForGateway(ep, &gtw, resourceMap)
		}
```

to 

```golang
		if !mergedGateways {
			var ep *egv1a1.EnvoyProxy = nil
			if gtw.Spec.Infrastructure != nil && gtw.Spec.Infrastructure.ParametersRef != nil {
				// processGatewayParamsRef has been called for this gateway, its EnvoyProxy should exist in resourceTree
				ep = resourceTree.GetEnvoyProxy(gtw.Namespace, gtw.Spec.Infrastructure.ParametersRef.Name)
			}
			if ep != nil {
				r.processServiceClusterForGateway(ep, &gtw, resourceMap)
			}
		}
```@zirain: fixed by https://github.com/envoyproxy/gateway/pull/6763?@qicz: > fixed by [#6763](https://github.com/envoyproxy/gateway/pull/6763)?

No, I am running the main branch with #6763@qicz: IMO, we do not support the EnvoyProxy only attached to the gatewayclass in the current version.


in `processServiceClusterForGateway`, we should check ep from gatwayclass like follows:

```golang
// Called on a Gateway when merged gateways mode is not enabled for its parent GatewayClass.
func (r *gatewayAPIReconciler) processServiceClusterForGateway(ep *egv1a1.EnvoyProxy, gateway *gwapiv1.Gateway, resourceMap *resourceMappings, resourceTree *resource.Resources) {
	// Skip processing if topology injector is disabled
	if r.envoyGateway != nil && r.envoyGateway.TopologyInjectorDisabled() {
		return
	}

	proxySvcName, proxySvcNamespace := proxy.ExpectedResourceHashedName(utils.NamespacedName(gateway).String()), r.namespace
	// Check if gateway namespaced mode is used. If it is, the service's name is the same as
	// the gateway's and it lives in the gateway's namespace not the controller's.
	if r.gatewayNamespaceMode {
		proxySvcName, proxySvcNamespace = gateway.Name, gateway.Namespace
	}

	// when gateway's EnvoyProxy is nil and gatewayclass 's EnvoyProxy is not nil
	// the specified proxySvcName should get from gatewayclass's EnvoyProxy
	if ep == nil && resourceTree.EnvoyProxyForGatewayClass != nil {
		ep = resourceTree.EnvoyProxyForGatewayClass
	}

	// Check if the service name was specified in EnvoyProxy
	if ep != nil {
		provider := ep.GetEnvoyProxyProvider()
		if provider.Kubernetes != nil && provider.Kubernetes.EnvoyService != nil && provider.Kubernetes.EnvoyService.Name != nil {
			proxySvcName = *provider.Kubernetes.EnvoyService.Name
		}
	}

	resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{
		Kind:      ptr.To(gwapiv1.Kind("Service")),
		Namespace: gatewayapi.NamespacePtr(proxySvcNamespace),
		Name:      gwapiv1.ObjectName(proxySvcName),
	})
}

```

Check the EnvoyProxy from gatewayclass again

```golang
	// when gateway's EnvoyProxy is nil and gatewayclass 's EnvoyProxy is not nil
	// the specified proxySvcName should get from gatewayclass's EnvoyProxy
	if ep == nil && resourceTree.EnvoyProxyForGatewayClass != nil {
		ep = resourceTree.EnvoyProxyForGatewayClass
	}
```@qicz: it is worked 

```golang
// when gateway's EnvoyProxy is nil and gatewayclass 's EnvoyProxy is not nil
	// the specified proxySvcName should get from gatewayclass's EnvoyProxy
	if ep == nil && resourceTree.EnvoyProxyForGatewayClass != nil {
		ep = resourceTree.EnvoyProxyForGatewayClass
	}
```

<img width="1563" height="865" alt="Image" src="https://github.com/user-attachments/assets/59a4e99f-795e-43b9-898d-cafcdc503867" />

Solution patch:
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index 051d56e2a..492bef67b 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -1483,6 +1483,11 @@ func (r *gatewayAPIReconciler) processGateways(ctx context.Context, managedGC *g
 				// processGatewayParamsRef has been called for this gateway, its EnvoyProxy should exist in resourceTree
 				ep = resourceTree.GetEnvoyProxy(gtw.Namespace, gtw.Spec.Infrastructure.ParametersRef.Name)
 			}
+			// when gateway's EnvoyProxy is nil and gatewayclass 's EnvoyProxy is not nil
+			// the specified proxySvcName should get from gatewayclass's EnvoyProxy
+			if ep == nil && resourceTree.EnvoyProxyForGatewayClass != nil {
+				ep = resourceTree.EnvoyProxyForGatewayClass
+			}
 			r.processServiceClusterForGateway(ep, &gtw, resourceMap)
 		}
 
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index e65d38c80..428d2defd 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -16,6 +16,7 @@ bug fixes: |
   Fixed the topologyInjectorDisabled and the local cluster was not defined.
   Fixed log formatting of improper key-value pairs to avoid DPANIC in controller-runtime logger.
   Fixed handling of context-related transient errors to prevent incorrect state reconciliation and unintended behavior.
+  Fixed the controller cannot read the EnvoyProxy attached gatewayclass only.
 
 # Enhancements that improve performance.
 performance improvements: |



Test patch:
diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go
index a19b8a46f..9d5228761 100644
--- a/internal/provider/kubernetes/controller_test.go
+++ b/internal/provider/kubernetes/controller_test.go
@@ -1314,12 +1314,13 @@ func TestProcessServiceClusterForGatewayClass(t *testing.T) {
 
 func TestProcessServiceClusterForGateway(t *testing.T) {
 	testCases := []struct {
-		name                  string
-		gateway               *gwapiv1.Gateway
-		envoyProxy            *egv1a1.EnvoyProxy
-		gatewayNamespacedMode bool
-		expectedSvcName       string
-		expectedSvcNamespace  string
+		name                   string
+		gateway                *gwapiv1.Gateway
+		envoyProxy             *egv1a1.EnvoyProxy
+		gatewayClassEnvoyProxy *egv1a1.EnvoyProxy
+		gatewayNamespacedMode  bool
+		expectedSvcName        string
+		expectedSvcNamespace   string
 	}{
 		{
 			name: "no gateway namespaced mode with no hardcoded service name",
@@ -1401,6 +1402,34 @@ func TestProcessServiceClusterForGateway(t *testing.T) {
 			expectedSvcName:       "my-gateway-svc",
 			expectedSvcNamespace:  "app-namespace",
 		},
+		{
+			name: "no gateway namespaced mode with no hardcoded service name attached gatewayclass",
+			gateway: &gwapiv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "my-gateway",
+					Namespace: "app-namespace",
+				},
+			},
+			envoyProxy: nil,
+			gatewayClassEnvoyProxy: &egv1a1.EnvoyProxy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "my-gateway",
+				},
+				Spec: egv1a1.EnvoyProxySpec{
+					Provider: &egv1a1.EnvoyProxyProvider{
+						Type: egv1a1.ProviderTypeKubernetes,
+						Kubernetes: &egv1a1.EnvoyProxyKubernetesProvider{
+							EnvoyService: &egv1a1.KubernetesServiceSpec{
+								Name: ptr.To("my-gateway-svc"),
+							},
+						},
+					},
+				},
+			},
+			gatewayNamespacedMode: false,
+			expectedSvcName:       "my-gateway-svc",
+			expectedSvcNamespace:  "",
+		},
 	}
 
 	for i := range testCases {
@@ -1422,6 +1451,10 @@ func TestProcessServiceClusterForGateway(t *testing.T) {
 				tc.expectedSvcName = proxy.ExpectedResourceHashedName(utils.NamespacedName(tc.gateway).String())
 			}
 
+			if tc.envoyProxy == nil && tc.gatewayClassEnvoyProxy != nil {
+				tc.envoyProxy = tc.gatewayClassEnvoyProxy
+			}
+
 			r.processServiceClusterForGateway(tc.envoyProxy, tc.gateway, resourceMap)
 
 			require.Contains(t, resourceMap.allAssociatedBackendRefs, gwapiv1.BackendObjectReference{