From 53a85ba3a2e0ba3758f51c7f0ed31e935b900c58 Mon Sep 17 00:00:00 2001 From: y-rabie Date: Wed, 19 Nov 2025 11:40:42 +0200 Subject: [PATCH 1/7] fix: aggregate xRoute/xPolicy statuses across GWCs in gateway-api runner Signed-off-by: y-rabie --- internal/gatewayapi/extensionserverpolicy.go | 41 ++- .../gatewayapi/extensionserverpolicy_test.go | 112 ++++++++ internal/gatewayapi/runner/runner.go | 267 ++++++++++++------ 3 files changed, 319 insertions(+), 101 deletions(-) diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index e378c270ba..8ecb208b96 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -81,7 +81,7 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst } if accepted { res = append(res, *policy) - policy.Object["status"] = policyStatusToUnstructured(policyStatus) + policy.Object["status"] = PolicyStatusToUnstructured(policyStatus) } } @@ -108,14 +108,6 @@ func extractTargetRefs(policy *unstructured.Unstructured, gateways []*GatewayCon return ret, nil } -func policyStatusToUnstructured(policyStatus gwapiv1.PolicyStatus) map[string]any { - ret := map[string]any{} - // No need to check the marshal/unmarshal error here - d, _ := json.Marshal(policyStatus) - _ = json.Unmarshal(d, &ret) - return ret -} - func resolveExtServerPolicyGatewayTargetRef(policy *unstructured.Unstructured, target gwapiv1.LocalPolicyTargetReferenceWithSectionName, gateways map[types.NamespacedName]*policyGatewayTargetContext) *GatewayContext { // Check if the gateway exists key := types.NamespacedName{ @@ -132,6 +124,29 @@ func resolveExtServerPolicyGatewayTargetRef(policy *unstructured.Unstructured, t return gateway.GatewayContext } +func PolicyStatusToUnstructured(policyStatus gwapiv1.PolicyStatus) map[string]any { + ret := map[string]any{} + // No need to check the marshal/unmarshal error here + d, _ := json.Marshal(policyStatus) + _ = json.Unmarshal(d, &ret) + return ret +} + +func ExtServerPolicyStatusAsPolicyStatus(policy *unstructured.Unstructured) gwapiv1.PolicyStatus { + statusObj := policy.Object["status"] + status := gwapiv1.PolicyStatus{} + if _, ok := statusObj.(map[string]any); ok { + // No need to check the json marshal/unmarshal error, the policyStatus was + // created via a typed object so the marshalling/unmarshalling will always + // work + d, _ := json.Marshal(statusObj) + _ = json.Unmarshal(d, &status) + } else if _, ok := statusObj.(gwapiv1.PolicyStatus); ok { + status = statusObj.(gwapiv1.PolicyStatus) + } + return status +} + func (t *Translator) translateExtServerPolicyForGateway( policy *unstructured.Unstructured, gateway *GatewayContext, @@ -173,3 +188,11 @@ func (t *Translator) translateExtServerPolicyForGateway( } return found } + +// Appends status ancestors from newPolicy into aggregatedPolicy's list of ancestors. +func MergeAncestorsForExtensionServerPolicies(aggregatedPolicy, newPolicy *unstructured.Unstructured) { + aggStatus := ExtServerPolicyStatusAsPolicyStatus(aggregatedPolicy) + newStatus := ExtServerPolicyStatusAsPolicyStatus(newPolicy) + aggStatus.Ancestors = append(aggStatus.Ancestors, newStatus.Ancestors...) + aggregatedPolicy.Object["status"] = PolicyStatusToUnstructured(aggStatus) +} diff --git a/internal/gatewayapi/extensionserverpolicy_test.go b/internal/gatewayapi/extensionserverpolicy_test.go index fbfc8418d6..73a9477e6e 100644 --- a/internal/gatewayapi/extensionserverpolicy_test.go +++ b/internal/gatewayapi/extensionserverpolicy_test.go @@ -123,3 +123,115 @@ func TestExtractTargetRefs(t *testing.T) { }) } } + +func TestMergeAncestorsForExtensionServerPolicies(t *testing.T) { + tests := []struct { + aggStatus *gwapiv1.PolicyStatus + newStatus *gwapiv1.PolicyStatus + noStatus bool + }{ + { + aggStatus: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{ + Name: "gateway-1", + }, + }, + }, + }, + newStatus: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{ + Name: "gateway-2", + }, + }, + }, + }, + }, + { + aggStatus: &gwapiv1.PolicyStatus{}, + newStatus: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{ + Name: "gateway-2", + }, + }, + }, + }, + }, + { + aggStatus: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{ + Name: "gateway-1", + }, + }, + }, + }, + newStatus: &gwapiv1.PolicyStatus{}, + }, + { + aggStatus: &gwapiv1.PolicyStatus{}, + newStatus: &gwapiv1.PolicyStatus{}, + }, + { + aggStatus: nil, + newStatus: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{ + Name: "gateway-1", + }, + }, + }, + }, + }, + { + aggStatus: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{ + Name: "gateway-1", + }, + }, + }, + }, + newStatus: nil, + }, + { + aggStatus: nil, + newStatus: nil, + }, + } + + for _, test := range tests { + aggPolicy := unstructured.Unstructured{Object: make(map[string]interface{})} + newPolicy := unstructured.Unstructured{Object: make(map[string]interface{})} + desiredMergedStatus := gwapiv1.PolicyStatus{} + + // aggStatus == nil, means simulate not setting status at all within the policy. + if test.aggStatus != nil { + aggPolicy.Object["status"] = PolicyStatusToUnstructured(*test.aggStatus) + desiredMergedStatus.Ancestors = append(desiredMergedStatus.Ancestors, test.aggStatus.Ancestors...) + } + + // newStatus == nil, means simulate not setting status at all within the policy. + if test.newStatus != nil { + newPolicy.Object["status"] = PolicyStatusToUnstructured(*test.newStatus) + desiredMergedStatus.Ancestors = append(desiredMergedStatus.Ancestors, test.newStatus.Ancestors...) + } + + MergeAncestorsForExtensionServerPolicies(&aggPolicy, &newPolicy) + + // The product object will always have an existing `status`, even if with 0 ancestors. + newAggPolicy := ExtServerPolicyStatusAsPolicyStatus(&aggPolicy) + require.Len(t, newAggPolicy.Ancestors, len(desiredMergedStatus.Ancestors)) + for i := range newAggPolicy.Ancestors { + require.Equal(t, desiredMergedStatus.Ancestors[i].AncestorRef.Name, newAggPolicy.Ancestors[i].AncestorRef.Name) + } + } +} diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 77dcf79481..4fc9273295 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -8,7 +8,6 @@ package runner import ( "context" "crypto/tls" - "encoding/json" "fmt" "os" "path" @@ -27,6 +26,7 @@ import ( "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/crypto" @@ -180,6 +180,55 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re var backendTLSPolicyStatusCount, clientTrafficPolicyStatusCount, backendTrafficPolicyStatusCount int var securityPolicyStatusCount, envoyExtensionPolicyStatusCount, backendStatusCount, extensionServerPolicyStatusCount int + // `aggregatedStatuses` aggregates status result of resources from all + // parents/ancestors, and then stores the status once for every resource. + aggregatedStatuses := struct { + HTTPRoutes map[types.NamespacedName]*gwapiv1.RouteStatus + GRPCRoutes map[types.NamespacedName]*gwapiv1.RouteStatus + TLSRoutes map[types.NamespacedName]*gwapiv1a2.RouteStatus + TCPRoutes map[types.NamespacedName]*gwapiv1a2.RouteStatus + UDPRoutes map[types.NamespacedName]*gwapiv1a2.RouteStatus + BackendTLSPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus + ClientTrafficPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus + BackendTrafficPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus + SecurityPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus + EnvoyExtensionPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus + ExtensionServerPolicies map[message.NamespacedNameAndGVK]*gwapiv1.PolicyStatus + }{ + HTTPRoutes: make(map[types.NamespacedName]*gwapiv1.RouteStatus), + GRPCRoutes: make(map[types.NamespacedName]*gwapiv1.RouteStatus), + TLSRoutes: make(map[types.NamespacedName]*gwapiv1a2.RouteStatus), + TCPRoutes: make(map[types.NamespacedName]*gwapiv1a2.RouteStatus), + UDPRoutes: make(map[types.NamespacedName]*gwapiv1a2.RouteStatus), + BackendTLSPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), + ClientTrafficPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), + BackendTrafficPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), + SecurityPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), + EnvoyExtensionPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), + ExtensionServerPolicies: make(map[message.NamespacedNameAndGVK]*gwapiv1.PolicyStatus), + } + + mergeRouteStatus := func(status, other *gwapiv1.RouteStatus) *gwapiv1.RouteStatus { + if other != nil { + if status != nil { + status.Parents = append(status.Parents, other.Parents...) + } else { + return other + } + } + return status + } + mergePolicyStatus := func(status, other *gwapiv1.PolicyStatus) *gwapiv1.PolicyStatus { + if other != nil { + if status != nil { + status.Ancestors = append(status.Ancestors, other.Ancestors...) + } else { + return other + } + } + return status + } + span.AddEvent("translate", trace.WithAttributes(attribute.Int("resources.count", len(*val)))) for _, resources := range *val { // Translate and publish IRs. @@ -267,6 +316,7 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re r.ProviderResources.GatewayClassStatuses.Store(key, &result.GatewayClass.Status) } + // 1. Resources which can only belong to 1 GatewayClass (at most) get their statuses stored right away. for _, gateway := range result.Gateways { key := utils.NamespacedName(gateway) r.ProviderResources.GatewayStatuses.Store(key, &gateway.Status) @@ -274,6 +324,15 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re delete(keysToDelete.GatewayStatus, key) r.keyCache.GatewayStatus[key] = true } + for _, backend := range result.Backends { + key := utils.NamespacedName(backend) + if len(backend.Status.Conditions) > 0 { + r.ProviderResources.BackendStatuses.Store(key, &backend.Status) + backendStatusCount++ + } + delete(keysToDelete.BackendStatus, key) + r.keyCache.BackendStatus[key] = true + } for _, xListenerSet := range result.XListenerSets { key := utils.NamespacedName(xListenerSet) r.ProviderResources.XListenerSetStatuses.Store(key, &xListenerSet.Status) @@ -281,119 +340,153 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re delete(keysToDelete.XListenerSetStatus, key) r.keyCache.XListenerSetStatus[key] = true } + // 2. Resources which can belong to multiple GatewayClasses get their + // status aggregated, then stored once after iterating over all GatewayClasses. for _, httpRoute := range result.HTTPRoutes { - key := utils.NamespacedName(httpRoute) - r.ProviderResources.HTTPRouteStatuses.Store(key, &httpRoute.Status) - httpRouteStatusCount++ - delete(keysToDelete.HTTPRouteStatus, key) - r.keyCache.HTTPRouteStatus[key] = true + if len(httpRoute.Status.Parents) != 0 { + key := utils.NamespacedName(httpRoute) + aggregatedStatuses.HTTPRoutes[key] = mergeRouteStatus(aggregatedStatuses.HTTPRoutes[key], &httpRoute.Status.RouteStatus) + } } for _, grpcRoute := range result.GRPCRoutes { - key := utils.NamespacedName(grpcRoute) - r.ProviderResources.GRPCRouteStatuses.Store(key, &grpcRoute.Status) - grpcRouteStatusCount++ - delete(keysToDelete.GRPCRouteStatus, key) - r.keyCache.GRPCRouteStatus[key] = true + if len(grpcRoute.Status.Parents) != 0 { + key := utils.NamespacedName(grpcRoute) + aggregatedStatuses.GRPCRoutes[key] = mergeRouteStatus(aggregatedStatuses.GRPCRoutes[key], &grpcRoute.Status.RouteStatus) + } } for _, tlsRoute := range result.TLSRoutes { - key := utils.NamespacedName(tlsRoute) - r.ProviderResources.TLSRouteStatuses.Store(key, &tlsRoute.Status) - tlsRouteStatusCount++ - delete(keysToDelete.TLSRouteStatus, key) - r.keyCache.TLSRouteStatus[key] = true + if len(tlsRoute.Status.Parents) != 0 { + key := utils.NamespacedName(tlsRoute) + aggregatedStatuses.TLSRoutes[key] = mergeRouteStatus(aggregatedStatuses.TLSRoutes[key], &tlsRoute.Status.RouteStatus) + } } for _, tcpRoute := range result.TCPRoutes { - key := utils.NamespacedName(tcpRoute) - r.ProviderResources.TCPRouteStatuses.Store(key, &tcpRoute.Status) - tcpRouteStatusCount++ - delete(keysToDelete.TCPRouteStatus, key) - r.keyCache.TCPRouteStatus[key] = true + if len(tcpRoute.Status.Parents) != 0 { + key := utils.NamespacedName(tcpRoute) + aggregatedStatuses.TCPRoutes[key] = mergeRouteStatus(aggregatedStatuses.TCPRoutes[key], &tcpRoute.Status.RouteStatus) + } } for _, udpRoute := range result.UDPRoutes { - key := utils.NamespacedName(udpRoute) - r.ProviderResources.UDPRouteStatuses.Store(key, &udpRoute.Status) - udpRouteStatusCount++ - delete(keysToDelete.UDPRouteStatus, key) - r.keyCache.UDPRouteStatus[key] = true + if len(udpRoute.Status.Parents) != 0 { + key := utils.NamespacedName(udpRoute) + aggregatedStatuses.UDPRoutes[key] = mergeRouteStatus(aggregatedStatuses.UDPRoutes[key], &udpRoute.Status.RouteStatus) + } } - - // Skip updating status for policies with empty status - // They may have been skipped in this translation because - // their target is not found (not relevant) - for _, backendTLSPolicy := range result.BackendTLSPolicies { - key := utils.NamespacedName(backendTLSPolicy) - if len(backendTLSPolicy.Status.Ancestors) > 0 { - r.ProviderResources.BackendTLSPolicyStatuses.Store(key, &backendTLSPolicy.Status) - backendTLSPolicyStatusCount++ + if len(backendTLSPolicy.Status.Ancestors) != 0 { + key := utils.NamespacedName(backendTLSPolicy) + aggregatedStatuses.BackendTLSPolicies[key] = mergePolicyStatus(aggregatedStatuses.BackendTLSPolicies[key], &backendTLSPolicy.Status) } - delete(keysToDelete.BackendTLSPolicyStatus, key) - r.keyCache.BackendTLSPolicyStatus[key] = true } - for _, clientTrafficPolicy := range result.ClientTrafficPolicies { - key := utils.NamespacedName(clientTrafficPolicy) - if len(clientTrafficPolicy.Status.Ancestors) > 0 { - r.ProviderResources.ClientTrafficPolicyStatuses.Store(key, &clientTrafficPolicy.Status) - clientTrafficPolicyStatusCount++ + if len(clientTrafficPolicy.Status.Ancestors) != 0 { + key := utils.NamespacedName(clientTrafficPolicy) + aggregatedStatuses.ClientTrafficPolicies[key] = mergePolicyStatus(aggregatedStatuses.ClientTrafficPolicies[key], &clientTrafficPolicy.Status) } - delete(keysToDelete.ClientTrafficPolicyStatus, key) - r.keyCache.ClientTrafficPolicyStatus[key] = true } for _, backendTrafficPolicy := range result.BackendTrafficPolicies { - key := utils.NamespacedName(backendTrafficPolicy) - if len(backendTrafficPolicy.Status.Ancestors) > 0 { - r.ProviderResources.BackendTrafficPolicyStatuses.Store(key, &backendTrafficPolicy.Status) - backendTrafficPolicyStatusCount++ + if len(backendTrafficPolicy.Status.Ancestors) != 0 { + key := utils.NamespacedName(backendTrafficPolicy) + aggregatedStatuses.BackendTrafficPolicies[key] = mergePolicyStatus(aggregatedStatuses.BackendTrafficPolicies[key], &backendTrafficPolicy.Status) } - delete(keysToDelete.BackendTrafficPolicyStatus, key) - r.keyCache.BackendTrafficPolicyStatus[key] = true } for _, securityPolicy := range result.SecurityPolicies { - key := utils.NamespacedName(securityPolicy) - if len(securityPolicy.Status.Ancestors) > 0 { - r.ProviderResources.SecurityPolicyStatuses.Store(key, &securityPolicy.Status) - securityPolicyStatusCount++ + if len(securityPolicy.Status.Ancestors) != 0 { + key := utils.NamespacedName(securityPolicy) + aggregatedStatuses.SecurityPolicies[key] = mergePolicyStatus(aggregatedStatuses.SecurityPolicies[key], &securityPolicy.Status) } - delete(keysToDelete.SecurityPolicyStatus, key) - r.keyCache.SecurityPolicyStatus[key] = true } for _, envoyExtensionPolicy := range result.EnvoyExtensionPolicies { - key := utils.NamespacedName(envoyExtensionPolicy) - if len(envoyExtensionPolicy.Status.Ancestors) > 0 { - r.ProviderResources.EnvoyExtensionPolicyStatuses.Store(key, &envoyExtensionPolicy.Status) - envoyExtensionPolicyStatusCount++ + if len(envoyExtensionPolicy.Status.Ancestors) != 0 { + key := utils.NamespacedName(envoyExtensionPolicy) + aggregatedStatuses.EnvoyExtensionPolicies[key] = mergePolicyStatus(aggregatedStatuses.EnvoyExtensionPolicies[key], &envoyExtensionPolicy.Status) } - delete(keysToDelete.EnvoyExtensionPolicyStatus, key) - r.keyCache.EnvoyExtensionPolicyStatus[key] = true - } - for _, backend := range result.Backends { - key := utils.NamespacedName(backend) - if len(backend.Status.Conditions) > 0 { - r.ProviderResources.BackendStatuses.Store(key, &backend.Status) - backendStatusCount++ - } - delete(keysToDelete.BackendStatus, key) - r.keyCache.BackendStatus[key] = true } for _, extServerPolicy := range result.ExtensionServerPolicies { - key := message.NamespacedNameAndGVK{ - NamespacedName: utils.NamespacedName(&extServerPolicy), - GroupVersionKind: extServerPolicy.GroupVersionKind(), - } - if statusObj, hasStatus := extServerPolicy.Object["status"]; hasStatus && statusObj != nil { - if statusMap, ok := statusObj.(map[string]any); ok && len(statusMap) > 0 { - policyStatus := unstructuredToPolicyStatus(statusMap) - r.ProviderResources.ExtensionPolicyStatuses.Store(key, &policyStatus) - extensionServerPolicyStatusCount++ + status := gatewayapi.ExtServerPolicyStatusAsPolicyStatus(&extServerPolicy) + if len(status.Ancestors) != 0 { + key := message.NamespacedNameAndGVK{ + NamespacedName: utils.NamespacedName(&extServerPolicy), + GroupVersionKind: extServerPolicy.GroupVersionKind(), } + aggregatedStatuses.ExtensionServerPolicies[key] = mergePolicyStatus(aggregatedStatuses.ExtensionServerPolicies[key], &status) } - delete(keysToDelete.ExtensionServerPolicyStatus, key) - r.keyCache.ExtensionServerPolicyStatus[key] = true } statusUpdateSpan.End() } + // Store the stauses of all objects atomically with the aggregated status. + for key, status := range aggregatedStatuses.HTTPRoutes { + s := gwapiv1.HTTPRouteStatus{RouteStatus: *status} + r.ProviderResources.HTTPRouteStatuses.Store(key, &s) + httpRouteStatusCount++ + delete(keysToDelete.HTTPRouteStatus, key) + r.keyCache.HTTPRouteStatus[key] = true + } + for key, status := range aggregatedStatuses.GRPCRoutes { + s := gwapiv1.GRPCRouteStatus{RouteStatus: *status} + r.ProviderResources.GRPCRouteStatuses.Store(key, &s) + grpcRouteStatusCount++ + delete(keysToDelete.GRPCRouteStatus, key) + r.keyCache.GRPCRouteStatus[key] = true + } + for key, status := range aggregatedStatuses.TLSRoutes { + s := gwapiv1a2.TLSRouteStatus{RouteStatus: *status} + r.ProviderResources.TLSRouteStatuses.Store(key, &s) + tlsRouteStatusCount++ + delete(keysToDelete.TLSRouteStatus, key) + r.keyCache.TLSRouteStatus[key] = true + } + for key, status := range aggregatedStatuses.TCPRoutes { + s := gwapiv1a2.TCPRouteStatus{RouteStatus: *status} + r.ProviderResources.TCPRouteStatuses.Store(key, &s) + tcpRouteStatusCount++ + delete(keysToDelete.TCPRouteStatus, key) + r.keyCache.TCPRouteStatus[key] = true + } + for key, status := range aggregatedStatuses.UDPRoutes { + s := gwapiv1a2.UDPRouteStatus{RouteStatus: *status} + r.ProviderResources.UDPRouteStatuses.Store(key, &s) + udpRouteStatusCount++ + delete(keysToDelete.UDPRouteStatus, key) + r.keyCache.UDPRouteStatus[key] = true + } + for key, status := range aggregatedStatuses.BackendTLSPolicies { + r.ProviderResources.BackendTLSPolicyStatuses.Store(key, status) + backendTLSPolicyStatusCount++ + delete(keysToDelete.BackendTLSPolicyStatus, key) + r.keyCache.BackendTLSPolicyStatus[key] = true + } + for key, status := range aggregatedStatuses.ClientTrafficPolicies { + r.ProviderResources.ClientTrafficPolicyStatuses.Store(key, status) + clientTrafficPolicyStatusCount++ + delete(keysToDelete.ClientTrafficPolicyStatus, key) + r.keyCache.ClientTrafficPolicyStatus[key] = true + } + for key, status := range aggregatedStatuses.BackendTrafficPolicies { + r.ProviderResources.BackendTrafficPolicyStatuses.Store(key, status) + backendTrafficPolicyStatusCount++ + delete(keysToDelete.BackendTrafficPolicyStatus, key) + r.keyCache.BackendTrafficPolicyStatus[key] = true + } + for key, status := range aggregatedStatuses.SecurityPolicies { + r.ProviderResources.SecurityPolicyStatuses.Store(key, status) + securityPolicyStatusCount++ + delete(keysToDelete.SecurityPolicyStatus, key) + r.keyCache.SecurityPolicyStatus[key] = true + } + for key, status := range aggregatedStatuses.EnvoyExtensionPolicies { + r.ProviderResources.EnvoyExtensionPolicyStatuses.Store(key, status) + envoyExtensionPolicyStatusCount++ + delete(keysToDelete.EnvoyExtensionPolicyStatus, key) + r.keyCache.EnvoyExtensionPolicyStatus[key] = true + } + for key, status := range aggregatedStatuses.ExtensionServerPolicies { + r.ProviderResources.ExtensionPolicyStatuses.Store(key, status) + extensionServerPolicyStatusCount++ + delete(keysToDelete.ExtensionServerPolicyStatus, key) + r.keyCache.ExtensionServerPolicyStatus[key] = true + } // Publish aggregated metrics message.PublishMetric(message.Metadata{Runner: r.Name(), Message: message.InfraIRMessageName}, infraIRCount) message.PublishMetric(message.Metadata{Runner: r.Name(), Message: message.XDSIRMessageName}, xdsIRCount) @@ -470,16 +563,6 @@ func (r *Runner) loadTLSConfig(ctx context.Context) (*tls.Config, []byte, error) } } -func unstructuredToPolicyStatus(policyStatus map[string]any) gwapiv1.PolicyStatus { - var ret gwapiv1.PolicyStatus - // No need to check the json marshal/unmarshal error, the policyStatus was - // created via a typed object so the marshalling/unmarshalling will always - // work - d, _ := json.Marshal(policyStatus) - _ = json.Unmarshal(d, &ret) - return ret -} - // deleteAllIRKeys deletes all XdsIR and InfraIR using tracked keys func (r *Runner) deleteAllKeys() { // Delete IR keys From a561b2232fd3841e5e0e3f25164c648661c4bade Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 3 Mar 2026 15:24:18 +0800 Subject: [PATCH 2/7] polish Signed-off-by: Huabing (Robin) Zhao --- internal/gatewayapi/extensionserverpolicy.go | 8 ------- .../gatewayapi/extensionserverpolicy_test.go | 10 +++++++- internal/gatewayapi/runner/runner.go | 23 +++++++++++-------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index 8ecb208b96..9a1b9ff2fc 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -188,11 +188,3 @@ func (t *Translator) translateExtServerPolicyForGateway( } return found } - -// Appends status ancestors from newPolicy into aggregatedPolicy's list of ancestors. -func MergeAncestorsForExtensionServerPolicies(aggregatedPolicy, newPolicy *unstructured.Unstructured) { - aggStatus := ExtServerPolicyStatusAsPolicyStatus(aggregatedPolicy) - newStatus := ExtServerPolicyStatusAsPolicyStatus(newPolicy) - aggStatus.Ancestors = append(aggStatus.Ancestors, newStatus.Ancestors...) - aggregatedPolicy.Object["status"] = PolicyStatusToUnstructured(aggStatus) -} diff --git a/internal/gatewayapi/extensionserverpolicy_test.go b/internal/gatewayapi/extensionserverpolicy_test.go index 73a9477e6e..751a76af99 100644 --- a/internal/gatewayapi/extensionserverpolicy_test.go +++ b/internal/gatewayapi/extensionserverpolicy_test.go @@ -225,7 +225,7 @@ func TestMergeAncestorsForExtensionServerPolicies(t *testing.T) { desiredMergedStatus.Ancestors = append(desiredMergedStatus.Ancestors, test.newStatus.Ancestors...) } - MergeAncestorsForExtensionServerPolicies(&aggPolicy, &newPolicy) + mergeAncestorsForExtensionServerPolicies(&aggPolicy, &newPolicy) // The product object will always have an existing `status`, even if with 0 ancestors. newAggPolicy := ExtServerPolicyStatusAsPolicyStatus(&aggPolicy) @@ -235,3 +235,11 @@ func TestMergeAncestorsForExtensionServerPolicies(t *testing.T) { } } } + +// Appends status ancestors from newPolicy into aggregatedPolicy's list of ancestors. +func mergeAncestorsForExtensionServerPolicies(aggregatedPolicy, newPolicy *unstructured.Unstructured) { + aggStatus := ExtServerPolicyStatusAsPolicyStatus(aggregatedPolicy) + newStatus := ExtServerPolicyStatusAsPolicyStatus(newPolicy) + aggStatus.Ancestors = append(aggStatus.Ancestors, newStatus.Ancestors...) + aggregatedPolicy.Object["status"] = PolicyStatusToUnstructured(aggStatus) +} diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 8aedda9c0c..f48c54cbbc 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -316,7 +316,7 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re r.ProviderResources.GatewayClassStatuses.Store(key, &result.GatewayClass.Status) } - // 1. Resources which can only belong to 1 GatewayClass (at most) get their statuses stored right away. + // Resources which can only belong to 1 GatewayClass (at most) get their statuses stored right away. for _, gateway := range result.Gateways { key := utils.NamespacedName(gateway) r.ProviderResources.GatewayStatuses.Store(key, &gateway.Status) @@ -324,6 +324,15 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re delete(keysToDelete.GatewayStatus, key) r.keyCache.GatewayStatus[key] = true } + for _, listenerSet := range result.ListenerSets { + key := utils.NamespacedName(listenerSet) + r.ProviderResources.ListenerSetStatuses.Store(key, &listenerSet.Status) + listenerSetStatusCount++ + delete(keysToDelete.ListenerSetStatus, key) + r.keyCache.ListenerSetStatus[key] = true + } + + // Backend statuses have no parents, so they are not aggregated. for _, backend := range result.Backends { key := utils.NamespacedName(backend) if len(backend.Status.Conditions) > 0 { @@ -333,15 +342,9 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re delete(keysToDelete.BackendStatus, key) r.keyCache.BackendStatus[key] = true } - for _, listenerSet := range result.ListenerSets { - key := utils.NamespacedName(listenerSet) - r.ProviderResources.ListenerSetStatuses.Store(key, &listenerSet.Status) - listenerSetStatusCount++ - delete(keysToDelete.ListenerSetStatus, key) - r.keyCache.ListenerSetStatus[key] = true - } - // 2. Resources which can belong to multiple GatewayClasses get their - // status aggregated, then stored once after iterating over all GatewayClasses. + + // Resources which can belong to multiple GatewayClasses get their statuses aggregated, + // then stored once after iterating over all GatewayClasses. for _, httpRoute := range result.HTTPRoutes { if len(httpRoute.Status.Parents) != 0 { key := utils.NamespacedName(httpRoute) From 71581c276e0074db31ccde7369889ff120df38e4 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 3 Mar 2026 15:57:07 +0800 Subject: [PATCH 3/7] add e2e test Signed-off-by: Huabing (Robin) Zhao --- .../httproute-status-multiple-gc.yaml | 102 ++++++++++++++++++ test/e2e/tests/multiple_gc.go | 59 +++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 test/e2e/testdata/httproute-status-multiple-gc.yaml diff --git a/test/e2e/testdata/httproute-status-multiple-gc.yaml b/test/e2e/testdata/httproute-status-multiple-gc.yaml new file mode 100644 index 0000000000..545e1ba837 --- /dev/null +++ b/test/e2e/testdata/httproute-status-multiple-gc.yaml @@ -0,0 +1,102 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: internet-gateway + namespace: gateway-conformance-infra +spec: + gatewayClassName: internet + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: private-gateway + namespace: gateway-conformance-infra +spec: + gatewayClassName: private + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same +--- +apiVersion: v1 +kind: Service +metadata: + name: multiple-gc-backend + namespace: gateway-conformance-infra +spec: + selector: + app: multiple-gc-backend + ports: + - protocol: TCP + port: 8080 + targetPort: 3000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: multiple-gc-backend + namespace: gateway-conformance-infra + labels: + app: multiple-gc-backend +spec: + replicas: 2 + selector: + matchLabels: + app: multiple-gc-backend + template: + metadata: + labels: + app: multiple-gc-backend + spec: + containers: + - name: multiple-gc-backend + image: gcr.io/k8s-staging-ingressconformance/echoserver:v20221109-7ee2f3e + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + requests: + cpu: 10m +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: multiple-gc-route + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: internet-gateway + sectionName: http + - name: private-gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: /internet + backendRefs: + - name: multiple-gc-backend + port: 8080 + - matches: + - path: + type: PathPrefix + value: /private + backendRefs: + - name: multiple-gc-backend + port: 8080 diff --git a/test/e2e/tests/multiple_gc.go b/test/e2e/tests/multiple_gc.go index 8d3a6c0cd5..5448b9a054 100644 --- a/test/e2e/tests/multiple_gc.go +++ b/test/e2e/tests/multiple_gc.go @@ -12,11 +12,14 @@ package tests import ( "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" + + gatewayapi "github.com/envoyproxy/gateway/internal/gatewayapi" ) var ( @@ -26,7 +29,7 @@ var ( func init() { MultipleGCTests = make(map[string][]suite.ConformanceTest) - InternetGCTests = append(InternetGCTests, InternetGCTest) + InternetGCTests = append(InternetGCTests, InternetGCTest, HTTPRouteStatusAggregatesAcrossGatewayClassesTest) PrivateGCTests = append(PrivateGCTests, PrivateGCTest) MultipleGCTests["internet"] = InternetGCTests MultipleGCTests["private"] = PrivateGCTests @@ -83,3 +86,57 @@ var PrivateGCTest = suite.ConformanceTest{ }) }, } + +var HTTPRouteStatusAggregatesAcrossGatewayClassesTest = suite.ConformanceTest{ + ShortName: "HTTPRouteStatusAggregatesAcrossGatewayClasses", + Description: "HTTPRoute status should aggregate parents across multiple GatewayClasses managed by the same controller", + Manifests: []string{"testdata/httproute-status-multiple-gc.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("httproute status aggregates across gateway classes", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "multiple-gc-route", Namespace: ns} + internetGatewayNN := types.NamespacedName{Name: "internet-gateway", Namespace: ns} + privateGatewayNN := types.NamespacedName{Name: "private-gateway", Namespace: ns} + + _, err := kubernetes.WaitForGatewayAddress(t, suite.Client, suite.TimeoutConfig, kubernetes.NewGatewayRef(internetGatewayNN)) + if err != nil { + t.Fatalf("failed to get %s Gateway address: %v", internetGatewayNN.Name, err) + } + + _, err = kubernetes.WaitForGatewayAddress(t, suite.Client, suite.TimeoutConfig, kubernetes.NewGatewayRef(privateGatewayNN)) + if err != nil { + t.Fatalf("failed to get %s Gateway address: %v", privateGatewayNN.Name, err) + } + + parents := []gwapiv1.RouteParentStatus{ + createGatewayParent(suite.ControllerName, internetGatewayNN.Name, "http"), + createGatewayParent(suite.ControllerName, privateGatewayNN.Name, "http"), + } + + kubernetes.RouteMustHaveParents(t, suite.Client, suite.TimeoutConfig, routeNN, parents, false, &gwapiv1.HTTPRoute{}) + }) + }, +} + +func createGatewayParent(controllerName, gatewayName, sectionName string) gwapiv1.RouteParentStatus { + return gwapiv1.RouteParentStatus{ + ParentRef: gwapiv1.ParentReference{ + Name: gwapiv1.ObjectName(gatewayName), + Namespace: gatewayapi.NamespacePtr("gateway-conformance-infra"), + SectionName: gatewayapi.SectionNamePtr(sectionName), + }, + ControllerName: gwapiv1.GatewayController(controllerName), + Conditions: []metav1.Condition{ + { + Type: string(gwapiv1.RouteConditionAccepted), + Status: metav1.ConditionTrue, + Reason: string(gwapiv1.RouteReasonAccepted), + }, + { + Type: string(gwapiv1.RouteConditionResolvedRefs), + Status: metav1.ConditionTrue, + Reason: string(gwapiv1.RouteReasonResolvedRefs), + }, + }, + } +} From 830d1c95136bd1fd8f0dd653939876e7730c1b5e Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 3 Mar 2026 16:18:45 +0800 Subject: [PATCH 4/7] release note Signed-off-by: Huabing (Robin) Zhao --- release-notes/current.yaml | 1 + test/e2e/tests/multiple_gc.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 03b595c1d4..cab57da5ab 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -36,6 +36,7 @@ bug fixes: | Fixed standalone mode emitting non-actionable error logs for missing secrets and unsupported ratelimit deletion on every startup. Fixed local object reference resolution from parent policy in merged BackendTrafficPolicies. Fixed xPolicy resources being processed from all namespaces when NamespaceSelector watch mode is configured in the Kubernetes provider. + Fixed route and policy status aggregation across multiple GatewayClasses managed by the same controller, so resources preserve status from all relevant parents and ancestors instead of being overwritten by the last processed GatewayClass. # Enhancements that improve performance. performance improvements: | diff --git a/test/e2e/tests/multiple_gc.go b/test/e2e/tests/multiple_gc.go index 5448b9a054..47237f5de4 100644 --- a/test/e2e/tests/multiple_gc.go +++ b/test/e2e/tests/multiple_gc.go @@ -121,6 +121,8 @@ var HTTPRouteStatusAggregatesAcrossGatewayClassesTest = suite.ConformanceTest{ func createGatewayParent(controllerName, gatewayName, sectionName string) gwapiv1.RouteParentStatus { return gwapiv1.RouteParentStatus{ ParentRef: gwapiv1.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupVersion.Group), + Kind: gatewayapi.KindPtr("Gateway"), Name: gwapiv1.ObjectName(gatewayName), Namespace: gatewayapi.NamespacePtr("gateway-conformance-infra"), SectionName: gatewayapi.SectionNamePtr(sectionName), From 7ef58b898ca690ceb733596ded7d13a0bbbe38da Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Thu, 5 Mar 2026 15:45:54 +0800 Subject: [PATCH 5/7] truncate policy status & add tests Signed-off-by: Huabing (Robin) Zhao --- internal/gatewayapi/runner/runner.go | 147 ++++++++++------- internal/gatewayapi/runner/runner_test.go | 156 ++++++++++++++++++ .../testdata/policy-status-multiple-gc.yaml | 52 ++++++ test/e2e/tests/multiple_gc.go | 64 ++++++- 4 files changed, 348 insertions(+), 71 deletions(-) create mode 100644 test/e2e/testdata/policy-status-multiple-gc.yaml diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index f48c54cbbc..a0f910a47a 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -34,6 +34,7 @@ import ( extension "github.com/envoyproxy/gateway/internal/extension/types" "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/infrastructure/host" "github.com/envoyproxy/gateway/internal/message" "github.com/envoyproxy/gateway/internal/utils" @@ -72,6 +73,41 @@ type Runner struct { done sync.WaitGroup } +type aggregatedPolicyStatus struct { + status *gwapiv1.PolicyStatus + generation int64 +} + +func mergeRouteStatus(status, other *gwapiv1.RouteStatus) *gwapiv1.RouteStatus { + if other != nil { + if status != nil { + status.Parents = append(status.Parents, other.Parents...) + } else { + return other + } + } + return status +} + +func mergePolicyStatus(aggregated aggregatedPolicyStatus, incoming *gwapiv1.PolicyStatus, generation int64) aggregatedPolicyStatus { + if incoming == nil { + return aggregated + } + + if aggregated.status == nil { + aggregated.status = incoming + aggregated.generation = generation + return aggregated + } + + aggregated.status.Ancestors = append(aggregated.status.Ancestors, incoming.Ancestors...) + if generation > aggregated.generation { + aggregated.generation = generation + } + + return aggregated +} + func New(cfg *Config) *Runner { return &Runner{ Config: *cfg, @@ -188,45 +224,24 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re TLSRoutes map[types.NamespacedName]*gwapiv1a2.RouteStatus TCPRoutes map[types.NamespacedName]*gwapiv1a2.RouteStatus UDPRoutes map[types.NamespacedName]*gwapiv1a2.RouteStatus - BackendTLSPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus - ClientTrafficPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus - BackendTrafficPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus - SecurityPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus - EnvoyExtensionPolicies map[types.NamespacedName]*gwapiv1.PolicyStatus - ExtensionServerPolicies map[message.NamespacedNameAndGVK]*gwapiv1.PolicyStatus + BackendTLSPolicies map[types.NamespacedName]aggregatedPolicyStatus + ClientTrafficPolicies map[types.NamespacedName]aggregatedPolicyStatus + BackendTrafficPolicies map[types.NamespacedName]aggregatedPolicyStatus + SecurityPolicies map[types.NamespacedName]aggregatedPolicyStatus + EnvoyExtensionPolicies map[types.NamespacedName]aggregatedPolicyStatus + ExtensionServerPolicies map[message.NamespacedNameAndGVK]aggregatedPolicyStatus }{ HTTPRoutes: make(map[types.NamespacedName]*gwapiv1.RouteStatus), GRPCRoutes: make(map[types.NamespacedName]*gwapiv1.RouteStatus), TLSRoutes: make(map[types.NamespacedName]*gwapiv1a2.RouteStatus), TCPRoutes: make(map[types.NamespacedName]*gwapiv1a2.RouteStatus), UDPRoutes: make(map[types.NamespacedName]*gwapiv1a2.RouteStatus), - BackendTLSPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), - ClientTrafficPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), - BackendTrafficPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), - SecurityPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), - EnvoyExtensionPolicies: make(map[types.NamespacedName]*gwapiv1.PolicyStatus), - ExtensionServerPolicies: make(map[message.NamespacedNameAndGVK]*gwapiv1.PolicyStatus), - } - - mergeRouteStatus := func(status, other *gwapiv1.RouteStatus) *gwapiv1.RouteStatus { - if other != nil { - if status != nil { - status.Parents = append(status.Parents, other.Parents...) - } else { - return other - } - } - return status - } - mergePolicyStatus := func(status, other *gwapiv1.PolicyStatus) *gwapiv1.PolicyStatus { - if other != nil { - if status != nil { - status.Ancestors = append(status.Ancestors, other.Ancestors...) - } else { - return other - } - } - return status + BackendTLSPolicies: make(map[types.NamespacedName]aggregatedPolicyStatus), + ClientTrafficPolicies: make(map[types.NamespacedName]aggregatedPolicyStatus), + BackendTrafficPolicies: make(map[types.NamespacedName]aggregatedPolicyStatus), + SecurityPolicies: make(map[types.NamespacedName]aggregatedPolicyStatus), + EnvoyExtensionPolicies: make(map[types.NamespacedName]aggregatedPolicyStatus), + ExtensionServerPolicies: make(map[message.NamespacedNameAndGVK]aggregatedPolicyStatus), } span.AddEvent("translate", trace.WithAttributes(attribute.Int("resources.count", len(*val)))) @@ -378,114 +393,120 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re for _, backendTLSPolicy := range result.BackendTLSPolicies { if len(backendTLSPolicy.Status.Ancestors) != 0 { key := utils.NamespacedName(backendTLSPolicy) - aggregatedStatuses.BackendTLSPolicies[key] = mergePolicyStatus(aggregatedStatuses.BackendTLSPolicies[key], &backendTLSPolicy.Status) + aggregatedStatuses.BackendTLSPolicies[key] = mergePolicyStatus(aggregatedStatuses.BackendTLSPolicies[key], &backendTLSPolicy.Status, backendTLSPolicy.Generation) } } for _, clientTrafficPolicy := range result.ClientTrafficPolicies { if len(clientTrafficPolicy.Status.Ancestors) != 0 { key := utils.NamespacedName(clientTrafficPolicy) - aggregatedStatuses.ClientTrafficPolicies[key] = mergePolicyStatus(aggregatedStatuses.ClientTrafficPolicies[key], &clientTrafficPolicy.Status) + aggregatedStatuses.ClientTrafficPolicies[key] = mergePolicyStatus(aggregatedStatuses.ClientTrafficPolicies[key], &clientTrafficPolicy.Status, clientTrafficPolicy.Generation) } } for _, backendTrafficPolicy := range result.BackendTrafficPolicies { if len(backendTrafficPolicy.Status.Ancestors) != 0 { key := utils.NamespacedName(backendTrafficPolicy) - aggregatedStatuses.BackendTrafficPolicies[key] = mergePolicyStatus(aggregatedStatuses.BackendTrafficPolicies[key], &backendTrafficPolicy.Status) + aggregatedStatuses.BackendTrafficPolicies[key] = mergePolicyStatus(aggregatedStatuses.BackendTrafficPolicies[key], &backendTrafficPolicy.Status, backendTrafficPolicy.Generation) } } for _, securityPolicy := range result.SecurityPolicies { if len(securityPolicy.Status.Ancestors) != 0 { key := utils.NamespacedName(securityPolicy) - aggregatedStatuses.SecurityPolicies[key] = mergePolicyStatus(aggregatedStatuses.SecurityPolicies[key], &securityPolicy.Status) + aggregatedStatuses.SecurityPolicies[key] = mergePolicyStatus(aggregatedStatuses.SecurityPolicies[key], &securityPolicy.Status, securityPolicy.Generation) } } for _, envoyExtensionPolicy := range result.EnvoyExtensionPolicies { if len(envoyExtensionPolicy.Status.Ancestors) != 0 { key := utils.NamespacedName(envoyExtensionPolicy) - aggregatedStatuses.EnvoyExtensionPolicies[key] = mergePolicyStatus(aggregatedStatuses.EnvoyExtensionPolicies[key], &envoyExtensionPolicy.Status) + aggregatedStatuses.EnvoyExtensionPolicies[key] = mergePolicyStatus(aggregatedStatuses.EnvoyExtensionPolicies[key], &envoyExtensionPolicy.Status, envoyExtensionPolicy.Generation) } } for _, extServerPolicy := range result.ExtensionServerPolicies { - status := gatewayapi.ExtServerPolicyStatusAsPolicyStatus(&extServerPolicy) - if len(status.Ancestors) != 0 { + policyStatus := gatewayapi.ExtServerPolicyStatusAsPolicyStatus(&extServerPolicy) + if len(policyStatus.Ancestors) != 0 { key := message.NamespacedNameAndGVK{ NamespacedName: utils.NamespacedName(&extServerPolicy), GroupVersionKind: extServerPolicy.GroupVersionKind(), } - aggregatedStatuses.ExtensionServerPolicies[key] = mergePolicyStatus(aggregatedStatuses.ExtensionServerPolicies[key], &status) + aggregatedStatuses.ExtensionServerPolicies[key] = mergePolicyStatus(aggregatedStatuses.ExtensionServerPolicies[key], &policyStatus, extServerPolicy.GetGeneration()) } } statusUpdateSpan.End() } // Store the stauses of all objects atomically with the aggregated status. - for key, status := range aggregatedStatuses.HTTPRoutes { - s := gwapiv1.HTTPRouteStatus{RouteStatus: *status} + for key, routeStatus := range aggregatedStatuses.HTTPRoutes { + s := gwapiv1.HTTPRouteStatus{RouteStatus: *routeStatus} r.ProviderResources.HTTPRouteStatuses.Store(key, &s) httpRouteStatusCount++ delete(keysToDelete.HTTPRouteStatus, key) r.keyCache.HTTPRouteStatus[key] = true } - for key, status := range aggregatedStatuses.GRPCRoutes { - s := gwapiv1.GRPCRouteStatus{RouteStatus: *status} + for key, routeStatus := range aggregatedStatuses.GRPCRoutes { + s := gwapiv1.GRPCRouteStatus{RouteStatus: *routeStatus} r.ProviderResources.GRPCRouteStatuses.Store(key, &s) grpcRouteStatusCount++ delete(keysToDelete.GRPCRouteStatus, key) r.keyCache.GRPCRouteStatus[key] = true } - for key, status := range aggregatedStatuses.TLSRoutes { - s := gwapiv1.TLSRouteStatus{RouteStatus: *status} + for key, routeStatus := range aggregatedStatuses.TLSRoutes { + s := gwapiv1.TLSRouteStatus{RouteStatus: *routeStatus} r.ProviderResources.TLSRouteStatuses.Store(key, &s) tlsRouteStatusCount++ delete(keysToDelete.TLSRouteStatus, key) r.keyCache.TLSRouteStatus[key] = true } - for key, status := range aggregatedStatuses.TCPRoutes { - s := gwapiv1a2.TCPRouteStatus{RouteStatus: *status} + for key, routeStatus := range aggregatedStatuses.TCPRoutes { + s := gwapiv1a2.TCPRouteStatus{RouteStatus: *routeStatus} r.ProviderResources.TCPRouteStatuses.Store(key, &s) tcpRouteStatusCount++ delete(keysToDelete.TCPRouteStatus, key) r.keyCache.TCPRouteStatus[key] = true } - for key, status := range aggregatedStatuses.UDPRoutes { - s := gwapiv1a2.UDPRouteStatus{RouteStatus: *status} + for key, routeStatus := range aggregatedStatuses.UDPRoutes { + s := gwapiv1a2.UDPRouteStatus{RouteStatus: *routeStatus} r.ProviderResources.UDPRouteStatuses.Store(key, &s) udpRouteStatusCount++ delete(keysToDelete.UDPRouteStatus, key) r.keyCache.UDPRouteStatus[key] = true } - for key, status := range aggregatedStatuses.BackendTLSPolicies { - r.ProviderResources.BackendTLSPolicyStatuses.Store(key, status) + for key, entry := range aggregatedStatuses.BackendTLSPolicies { + status.TruncatePolicyAncestors(entry.status, r.EnvoyGateway.Gateway.ControllerName, entry.generation) + r.ProviderResources.BackendTLSPolicyStatuses.Store(key, entry.status) backendTLSPolicyStatusCount++ delete(keysToDelete.BackendTLSPolicyStatus, key) r.keyCache.BackendTLSPolicyStatus[key] = true } - for key, status := range aggregatedStatuses.ClientTrafficPolicies { - r.ProviderResources.ClientTrafficPolicyStatuses.Store(key, status) + for key, entry := range aggregatedStatuses.ClientTrafficPolicies { + status.TruncatePolicyAncestors(entry.status, r.EnvoyGateway.Gateway.ControllerName, entry.generation) + r.ProviderResources.ClientTrafficPolicyStatuses.Store(key, entry.status) clientTrafficPolicyStatusCount++ delete(keysToDelete.ClientTrafficPolicyStatus, key) r.keyCache.ClientTrafficPolicyStatus[key] = true } - for key, status := range aggregatedStatuses.BackendTrafficPolicies { - r.ProviderResources.BackendTrafficPolicyStatuses.Store(key, status) + for key, entry := range aggregatedStatuses.BackendTrafficPolicies { + status.TruncatePolicyAncestors(entry.status, r.EnvoyGateway.Gateway.ControllerName, entry.generation) + r.ProviderResources.BackendTrafficPolicyStatuses.Store(key, entry.status) backendTrafficPolicyStatusCount++ delete(keysToDelete.BackendTrafficPolicyStatus, key) r.keyCache.BackendTrafficPolicyStatus[key] = true } - for key, status := range aggregatedStatuses.SecurityPolicies { - r.ProviderResources.SecurityPolicyStatuses.Store(key, status) + for key, entry := range aggregatedStatuses.SecurityPolicies { + status.TruncatePolicyAncestors(entry.status, r.EnvoyGateway.Gateway.ControllerName, entry.generation) + r.ProviderResources.SecurityPolicyStatuses.Store(key, entry.status) securityPolicyStatusCount++ delete(keysToDelete.SecurityPolicyStatus, key) r.keyCache.SecurityPolicyStatus[key] = true } - for key, status := range aggregatedStatuses.EnvoyExtensionPolicies { - r.ProviderResources.EnvoyExtensionPolicyStatuses.Store(key, status) + for key, entry := range aggregatedStatuses.EnvoyExtensionPolicies { + status.TruncatePolicyAncestors(entry.status, r.EnvoyGateway.Gateway.ControllerName, entry.generation) + r.ProviderResources.EnvoyExtensionPolicyStatuses.Store(key, entry.status) envoyExtensionPolicyStatusCount++ delete(keysToDelete.EnvoyExtensionPolicyStatus, key) r.keyCache.EnvoyExtensionPolicyStatus[key] = true } - for key, status := range aggregatedStatuses.ExtensionServerPolicies { - r.ProviderResources.ExtensionPolicyStatuses.Store(key, status) + for key, entry := range aggregatedStatuses.ExtensionServerPolicies { + status.TruncatePolicyAncestors(entry.status, r.EnvoyGateway.Gateway.ControllerName, entry.generation) + r.ProviderResources.ExtensionPolicyStatuses.Store(key, entry.status) extensionServerPolicyStatusCount++ delete(keysToDelete.ExtensionServerPolicyStatus, key) r.keyCache.ExtensionServerPolicyStatus[key] = true diff --git a/internal/gatewayapi/runner/runner_test.go b/internal/gatewayapi/runner/runner_test.go index 674654d4c0..a4cc1a4448 100644 --- a/internal/gatewayapi/runner/runner_test.go +++ b/internal/gatewayapi/runner/runner_test.go @@ -23,6 +23,7 @@ import ( "github.com/envoyproxy/gateway/internal/crypto" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/extension/registry" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/message" pb "github.com/envoyproxy/gateway/proto/extension" @@ -240,6 +241,161 @@ func TestDeleteAllKeys(t *testing.T) { require.Empty(t, r.keyCache.EnvoyExtensionPolicyStatus) } +func TestMergePolicyStatus(t *testing.T) { + controllerName := "example.com/gateway" + + t.Run("nil incoming keeps existing entry", func(t *testing.T) { + existing := aggregatedPolicyStatus{ + status: &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}, + ControllerName: gwapiv1a2.GatewayController(controllerName), + }, + }, + }, + generation: 2, + } + + got := mergePolicyStatus(existing, nil, 10) + require.Equal(t, existing, got) + }) + + t.Run("nil existing takes incoming", func(t *testing.T) { + incoming := &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}, + ControllerName: gwapiv1a2.GatewayController(controllerName), + }, + }, + } + + got := mergePolicyStatus(aggregatedPolicyStatus{}, incoming, 7) + require.Same(t, incoming, got.status) + require.Equal(t, int64(7), got.generation) + }) + + t.Run("appends ancestors and tracks max generation", func(t *testing.T) { + first := &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}, + ControllerName: gwapiv1a2.GatewayController(controllerName), + }, + }, + } + second := &gwapiv1.PolicyStatus{ + Ancestors: []gwapiv1.PolicyAncestorStatus{ + { + AncestorRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-b")}, + ControllerName: gwapiv1a2.GatewayController(controllerName), + }, + }, + } + + entry := mergePolicyStatus(aggregatedPolicyStatus{}, first, 3) + entry = mergePolicyStatus(entry, second, 9) + + require.Len(t, entry.status.Ancestors, 2) + require.Equal(t, gwapiv1.ObjectName("gw-a"), entry.status.Ancestors[0].AncestorRef.Name) + require.Equal(t, gwapiv1.ObjectName("gw-b"), entry.status.Ancestors[1].AncestorRef.Name) + require.Equal(t, int64(9), entry.generation) + + entry = mergePolicyStatus(entry, &gwapiv1.PolicyStatus{}, 4) + require.Equal(t, int64(9), entry.generation) + }) +} + +func TestMergeRouteStatus(t *testing.T) { + t.Run("nil other keeps existing status", func(t *testing.T) { + existing := &gwapiv1.RouteStatus{ + Parents: []gwapiv1.RouteParentStatus{ + {ParentRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}}, + }, + } + + got := mergeRouteStatus(existing, nil) + require.Same(t, existing, got) + require.Len(t, got.Parents, 1) + }) + + t.Run("nil existing takes other", func(t *testing.T) { + other := &gwapiv1.RouteStatus{ + Parents: []gwapiv1.RouteParentStatus{ + {ParentRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}}, + }, + } + + got := mergeRouteStatus(nil, other) + require.Same(t, other, got) + require.Len(t, got.Parents, 1) + }) + + t.Run("appends parents", func(t *testing.T) { + existing := &gwapiv1.RouteStatus{ + Parents: []gwapiv1.RouteParentStatus{ + {ParentRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}}, + }, + } + other := &gwapiv1.RouteStatus{ + Parents: []gwapiv1.RouteParentStatus{ + {ParentRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-b")}}, + }, + } + + got := mergeRouteStatus(existing, other) + require.Same(t, existing, got) + require.Len(t, got.Parents, 2) + require.Equal(t, gwapiv1.ObjectName("gw-a"), got.Parents[0].ParentRef.Name) + require.Equal(t, gwapiv1.ObjectName("gw-b"), got.Parents[1].ParentRef.Name) + }) +} + +func TestMergePolicyStatusTruncation(t *testing.T) { + controllerName := "example.com/gateway" + const generation int64 = 42 + + base := &gwapiv1.PolicyStatus{} + for i := 0; i < 10; i++ { + base.Ancestors = append(base.Ancestors, gwapiv1.PolicyAncestorStatus{ + AncestorRef: gwapiv1.ParentReference{ + Name: gwapiv1.ObjectName("gw-base-" + string(rune('a'+i))), + }, + ControllerName: gwapiv1a2.GatewayController(controllerName), + }) + } + + incoming := &gwapiv1.PolicyStatus{} + for i := 0; i < 10; i++ { + incoming.Ancestors = append(incoming.Ancestors, gwapiv1.PolicyAncestorStatus{ + AncestorRef: gwapiv1.ParentReference{ + Name: gwapiv1.ObjectName("gw-incoming-" + string(rune('a'+i))), + }, + ControllerName: gwapiv1a2.GatewayController(controllerName), + }) + } + + entry := mergePolicyStatus(aggregatedPolicyStatus{}, base, generation) + entry = mergePolicyStatus(entry, incoming, generation) + + require.Len(t, entry.status.Ancestors, 20) + status.TruncatePolicyAncestors(entry.status, controllerName, entry.generation) + + require.Len(t, entry.status.Ancestors, 16) + + last := entry.status.Ancestors[15] + foundAggregated := false + for _, cond := range last.Conditions { + if cond.Type == string(egv1a1.PolicyConditionAggregated) { + foundAggregated = true + require.Equal(t, string(egv1a1.PolicyReasonAggregated), cond.Reason) + require.Equal(t, generation, cond.ObservedGeneration) + } + } + require.True(t, foundAggregated, "expected aggregated condition on the truncated ancestor") +} + func TestLoadTLSConfig_HostMode(t *testing.T) { // Create temporary directory structure for certs using t.TempDir() configHome := t.TempDir() diff --git a/test/e2e/testdata/policy-status-multiple-gc.yaml b/test/e2e/testdata/policy-status-multiple-gc.yaml new file mode 100644 index 0000000000..712e39ba80 --- /dev/null +++ b/test/e2e/testdata/policy-status-multiple-gc.yaml @@ -0,0 +1,52 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: internet-gateway + namespace: gateway-conformance-infra +spec: + gatewayClassName: internet + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: private-gateway + namespace: gateway-conformance-infra +spec: + gatewayClassName: private + listeners: + - name: http + port: 80 + protocol: HTTP + allowedRoutes: + namespaces: + from: Same +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: multiple-gc-btp + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: internet-gateway + sectionName: http + - group: gateway.networking.k8s.io + kind: Gateway + name: private-gateway + sectionName: http + responseOverride: + - match: + statusCodes: + - type: Value + value: 418 + response: + statusCode: 500 diff --git a/test/e2e/tests/multiple_gc.go b/test/e2e/tests/multiple_gc.go index 47237f5de4..a7bb1ad16e 100644 --- a/test/e2e/tests/multiple_gc.go +++ b/test/e2e/tests/multiple_gc.go @@ -10,15 +10,20 @@ package tests import ( + "context" "testing" + "time" + "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" gatewayapi "github.com/envoyproxy/gateway/internal/gatewayapi" ) @@ -29,7 +34,7 @@ var ( func init() { MultipleGCTests = make(map[string][]suite.ConformanceTest) - InternetGCTests = append(InternetGCTests, InternetGCTest, HTTPRouteStatusAggregatesAcrossGatewayClassesTest) + InternetGCTests = append(InternetGCTests, InternetGCTest, HTTPRouteStatusAggregatesAcrossGatewayClassesTest, PolicyStatusAggregatesAcrossGatewayClassesTest) PrivateGCTests = append(PrivateGCTests, PrivateGCTest) MultipleGCTests["internet"] = InternetGCTests MultipleGCTests["private"] = PrivateGCTests @@ -118,15 +123,58 @@ var HTTPRouteStatusAggregatesAcrossGatewayClassesTest = suite.ConformanceTest{ }, } +var PolicyStatusAggregatesAcrossGatewayClassesTest = suite.ConformanceTest{ + ShortName: "PolicyStatusAggregatesAcrossGatewayClasses", + Description: "BackendTrafficPolicy status should aggregate ancestors across multiple GatewayClasses managed by the same controller", + Manifests: []string{"testdata/policy-status-multiple-gc.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("backendtrafficpolicy status aggregates across gateway classes", func(t *testing.T) { + ns := "gateway-conformance-infra" + policyNN := types.NamespacedName{Name: "multiple-gc-btp", Namespace: ns} + internetGatewayNN := types.NamespacedName{Name: "internet-gateway", Namespace: ns} + privateGatewayNN := types.NamespacedName{Name: "private-gateway", Namespace: ns} + + _, err := kubernetes.WaitForGatewayAddress(t, suite.Client, suite.TimeoutConfig, kubernetes.NewGatewayRef(internetGatewayNN)) + if err != nil { + t.Fatalf("failed to get %s Gateway address: %v", internetGatewayNN.Name, err) + } + + _, err = kubernetes.WaitForGatewayAddress(t, suite.Client, suite.TimeoutConfig, kubernetes.NewGatewayRef(privateGatewayNN)) + if err != nil { + t.Fatalf("failed to get %s Gateway address: %v", privateGatewayNN.Name, err) + } + + internetAncestorRef := createGatewayPolicyAncestorRef(internetGatewayNN.Name, "http") + privateAncestorRef := createGatewayPolicyAncestorRef(privateGatewayNN.Name, "http") + + waitErr := wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, true, func(ctx context.Context) (bool, error) { + policy := &egv1a1.BackendTrafficPolicy{} + if err := suite.Client.Get(ctx, policyNN, policy); err != nil { + return false, err + } + + return policyAcceptedByAncestor(policy.Status.Ancestors, suite.ControllerName, internetAncestorRef) && + policyAcceptedByAncestor(policy.Status.Ancestors, suite.ControllerName, privateAncestorRef), nil + }) + + require.NoErrorf(t, waitErr, "error waiting for BackendTrafficPolicy status ancestors to aggregate across gateway classes") + }) + }, +} + +func createGatewayPolicyAncestorRef(gatewayName, sectionName string) gwapiv1.ParentReference { + return gwapiv1.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupVersion.Group), + Kind: gatewayapi.KindPtr("Gateway"), + Name: gwapiv1.ObjectName(gatewayName), + Namespace: gatewayapi.NamespacePtr("gateway-conformance-infra"), + SectionName: gatewayapi.SectionNamePtr(sectionName), + } +} + func createGatewayParent(controllerName, gatewayName, sectionName string) gwapiv1.RouteParentStatus { return gwapiv1.RouteParentStatus{ - ParentRef: gwapiv1.ParentReference{ - Group: gatewayapi.GroupPtr(gwapiv1.GroupVersion.Group), - Kind: gatewayapi.KindPtr("Gateway"), - Name: gwapiv1.ObjectName(gatewayName), - Namespace: gatewayapi.NamespacePtr("gateway-conformance-infra"), - SectionName: gatewayapi.SectionNamePtr(sectionName), - }, + ParentRef: createGatewayPolicyAncestorRef(gatewayName, sectionName), ControllerName: gwapiv1.GatewayController(controllerName), Conditions: []metav1.Condition{ { From c5fc60faa7238b5388ba19ebd81dcf3c7e42a0a7 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Thu, 5 Mar 2026 15:56:06 +0800 Subject: [PATCH 6/7] update Signed-off-by: Huabing (Robin) Zhao --- internal/gatewayapi/runner/runner.go | 13 +++---- internal/gatewayapi/runner/runner_test.go | 44 ----------------------- 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index a0f910a47a..0908614d65 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -78,15 +78,16 @@ type aggregatedPolicyStatus struct { generation int64 } -func mergeRouteStatus(status, other *gwapiv1.RouteStatus) *gwapiv1.RouteStatus { - if other != nil { - if status != nil { - status.Parents = append(status.Parents, other.Parents...) +// TODO: zhaohuabing - truncate the parents to the max allowed(32) +func mergeRouteStatus(aggregated, incoming *gwapiv1.RouteStatus) *gwapiv1.RouteStatus { + if incoming != nil { + if aggregated != nil { + aggregated.Parents = append(aggregated.Parents, incoming.Parents...) } else { - return other + return incoming } } - return status + return aggregated } func mergePolicyStatus(aggregated aggregatedPolicyStatus, incoming *gwapiv1.PolicyStatus, generation int64) aggregatedPolicyStatus { diff --git a/internal/gatewayapi/runner/runner_test.go b/internal/gatewayapi/runner/runner_test.go index a4cc1a4448..e5cbfcca35 100644 --- a/internal/gatewayapi/runner/runner_test.go +++ b/internal/gatewayapi/runner/runner_test.go @@ -352,50 +352,6 @@ func TestMergeRouteStatus(t *testing.T) { }) } -func TestMergePolicyStatusTruncation(t *testing.T) { - controllerName := "example.com/gateway" - const generation int64 = 42 - - base := &gwapiv1.PolicyStatus{} - for i := 0; i < 10; i++ { - base.Ancestors = append(base.Ancestors, gwapiv1.PolicyAncestorStatus{ - AncestorRef: gwapiv1.ParentReference{ - Name: gwapiv1.ObjectName("gw-base-" + string(rune('a'+i))), - }, - ControllerName: gwapiv1a2.GatewayController(controllerName), - }) - } - - incoming := &gwapiv1.PolicyStatus{} - for i := 0; i < 10; i++ { - incoming.Ancestors = append(incoming.Ancestors, gwapiv1.PolicyAncestorStatus{ - AncestorRef: gwapiv1.ParentReference{ - Name: gwapiv1.ObjectName("gw-incoming-" + string(rune('a'+i))), - }, - ControllerName: gwapiv1a2.GatewayController(controllerName), - }) - } - - entry := mergePolicyStatus(aggregatedPolicyStatus{}, base, generation) - entry = mergePolicyStatus(entry, incoming, generation) - - require.Len(t, entry.status.Ancestors, 20) - status.TruncatePolicyAncestors(entry.status, controllerName, entry.generation) - - require.Len(t, entry.status.Ancestors, 16) - - last := entry.status.Ancestors[15] - foundAggregated := false - for _, cond := range last.Conditions { - if cond.Type == string(egv1a1.PolicyConditionAggregated) { - foundAggregated = true - require.Equal(t, string(egv1a1.PolicyReasonAggregated), cond.Reason) - require.Equal(t, generation, cond.ObservedGeneration) - } - } - require.True(t, foundAggregated, "expected aggregated condition on the truncated ancestor") -} - func TestLoadTLSConfig_HostMode(t *testing.T) { // Create temporary directory structure for certs using t.TempDir() configHome := t.TempDir() From 646784f9e9d0ccd4b326de368969129a64aa0a00 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Thu, 5 Mar 2026 16:05:06 +0800 Subject: [PATCH 7/7] update Signed-off-by: Huabing (Robin) Zhao --- internal/gatewayapi/runner/runner_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/gatewayapi/runner/runner_test.go b/internal/gatewayapi/runner/runner_test.go index e5cbfcca35..81133ab7ad 100644 --- a/internal/gatewayapi/runner/runner_test.go +++ b/internal/gatewayapi/runner/runner_test.go @@ -23,7 +23,6 @@ import ( "github.com/envoyproxy/gateway/internal/crypto" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/extension/registry" - "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/message" pb "github.com/envoyproxy/gateway/proto/extension" @@ -308,7 +307,7 @@ func TestMergePolicyStatus(t *testing.T) { } func TestMergeRouteStatus(t *testing.T) { - t.Run("nil other keeps existing status", func(t *testing.T) { + t.Run("nil incoming keeps existing status", func(t *testing.T) { existing := &gwapiv1.RouteStatus{ Parents: []gwapiv1.RouteParentStatus{ {ParentRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}}, @@ -320,15 +319,15 @@ func TestMergeRouteStatus(t *testing.T) { require.Len(t, got.Parents, 1) }) - t.Run("nil existing takes other", func(t *testing.T) { - other := &gwapiv1.RouteStatus{ + t.Run("nil existing takes incoming", func(t *testing.T) { + incoming := &gwapiv1.RouteStatus{ Parents: []gwapiv1.RouteParentStatus{ {ParentRef: gwapiv1.ParentReference{Name: gwapiv1.ObjectName("gw-a")}}, }, } - got := mergeRouteStatus(nil, other) - require.Same(t, other, got) + got := mergeRouteStatus(nil, incoming) + require.Same(t, incoming, got) require.Len(t, got.Parents, 1) })