Skip to content

Commit a9bef03

Browse files
committed
fix/refactor: deep copy status in translator for all resources
Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
1 parent 688ade8 commit a9bef03

11 files changed

Lines changed: 122 additions & 187 deletions

internal/cmd/egctl/translate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,10 @@ func translateGatewayAPIToGatewayAPI(resources *resource.Resources) (resource.Re
338338
msg := fmt.Sprintf("%s: %v", status.MsgGatewayClassInvalidParams, err)
339339
status.SetGatewayClassAccepted(resources.GatewayClass, false, string(gwapiv1.GatewayClassReasonInvalidParameters), msg)
340340
}
341-
gRes.EnvoyProxyForGatewayClass = resources.EnvoyProxyForGatewayClass
341+
// Don't overwrite gRes.EnvoyProxyForGatewayClass here: the translator already
342+
// populated it with the status-isolated copy carrying the computed Accepted
343+
// condition. resources.EnvoyProxyForGatewayClass is the original object, whose
344+
// Status the translator no longer mutates in place (see Translate's StatusDeepCopy).
342345
}
343346

344347
if !epInvalid {

internal/gatewayapi/backend.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ import (
2020
)
2121

2222
func (t *Translator) ProcessBackends(backends []*egv1a1.Backend, backendTLSPolicies []*gwapiv1.BackendTLSPolicy) []*egv1a1.Backend {
23-
backendCopies := backendCopiesWithStatusDeepCopy(backends)
2423
res := make([]*egv1a1.Backend, 0, len(backends))
25-
for i := range backends {
26-
backend := backendCopies[i]
24+
for _, backend := range backends {
2725
// Ensure Backends are enabled
2826
if !t.BackendEnabled {
2927
status.UpdateBackendStatusAcceptedCondition(backend, false,
@@ -207,14 +205,3 @@ func validateIP(epIP *egv1a1.IPEndpoint, runningOnHost bool) status.Error {
207205
return nil
208206
}
209207

210-
// backendCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
211-
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
212-
func backendCopiesWithStatusDeepCopy(backends []*egv1a1.Backend) []*egv1a1.Backend {
213-
copies := make([]*egv1a1.Backend, len(backends))
214-
for i, b := range backends {
215-
out := *b
216-
b.Status.DeepCopyInto(&out.Status)
217-
copies[i] = &out
218-
}
219-
return copies
220-
}

internal/gatewayapi/backendtrafficpolicy.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ func (t *Translator) ProcessBackendTrafficPolicies(
259259
SectionIndex: make(map[types.NamespacedName]sets.Set[string], gatewayMapSize),
260260
}
261261

262-
policyCopies := backendTrafficPolicyCopiesWithStatusDeepCopy(backendTrafficPolicies)
263-
264262
handledPolicies := make(map[types.NamespacedName]*egv1a1.BackendTrafficPolicy, policyMapSize)
265263

266264
// Translate
@@ -281,7 +279,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
281279
if isRouteRule(currTarget) {
282280
policy, found := handledPolicies[policyName]
283281
if !found {
284-
policy = policyCopies[i]
282+
policy = backendTrafficPolicies[i]
285283
handledPolicies[policyName] = policy
286284
res = append(res, policy)
287285
}
@@ -307,7 +305,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
307305
if isRoute(currTarget) {
308306
policy, found := handledPolicies[policyName]
309307
if !found {
310-
policy = policyCopies[i]
308+
policy = backendTrafficPolicies[i]
311309
handledPolicies[policyName] = policy
312310
res = append(res, policy)
313311
}
@@ -327,7 +325,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
327325
if isListener(currTarget) {
328326
policy, found := handledPolicies[policyName]
329327
if !found {
330-
policy = policyCopies[i]
328+
policy = backendTrafficPolicies[i]
331329
handledPolicies[policyName] = policy
332330
res = append(res, policy)
333331
}
@@ -352,7 +350,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
352350
if isGateway(currTarget) {
353351
policy, found := handledPolicies[policyName]
354352
if !found {
355-
policy = policyCopies[i]
353+
policy = backendTrafficPolicies[i]
356354
handledPolicies[policyName] = policy
357355
res = append(res, policy)
358356
}
@@ -2196,15 +2194,3 @@ func buildRouteStatName(routeStatName string, metadata *ir.ResourceMetadata) *st
21962194

21972195
return &statName
21982196
}
2199-
2200-
// backendTrafficPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
2201-
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
2202-
func backendTrafficPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.BackendTrafficPolicy) []*egv1a1.BackendTrafficPolicy {
2203-
copies := make([]*egv1a1.BackendTrafficPolicy, len(policies))
2204-
for i, p := range policies {
2205-
out := *p
2206-
p.Status.DeepCopyInto(&out.Status)
2207-
copies[i] = &out
2208-
}
2209-
return copies
2210-
}

internal/gatewayapi/clienttrafficpolicy.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,6 @@ func (t *Translator) ProcessClientTrafficPolicies(
9696
listenerSetMap[key] = ls
9797
}
9898

99-
policyCopies := clientTrafficPolicyCopiesWithStatusDeepCopy(clientTrafficPolicies)
100-
10199
handledPolicies := make(map[types.NamespacedName]*egv1a1.ClientTrafficPolicy)
102100
// Translate
103101
// 1. First translate Policies with a sectionName set
@@ -112,7 +110,7 @@ func (t *Translator) ProcessClientTrafficPolicies(
112110
if hasSectionName(&targetRef) {
113111
policy, found := handledPolicies[policyName]
114112
if !found {
115-
policy = policyCopies[i]
113+
policy = clientTrafficPolicies[i]
116114
handledPolicies[policyName] = policy
117115
res = append(res, policy)
118116
}
@@ -258,7 +256,7 @@ func (t *Translator) ProcessClientTrafficPolicies(
258256

259257
policy, found := handledPolicies[policyName]
260258
if !found {
261-
policy = policyCopies[i]
259+
policy = clientTrafficPolicies[i]
262260
res = append(res, policy)
263261
handledPolicies[policyName] = policy
264262
}
@@ -1507,15 +1505,3 @@ func translateHeaderModifier(headerModifier *egv1a1.HTTPHeaderFilter, modType st
15071505

15081506
return addRequestHeaders, removeRequestHeaders, removeRequestHeadersOnMatch, errs
15091507
}
1510-
1511-
// clientTrafficPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
1512-
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
1513-
func clientTrafficPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.ClientTrafficPolicy) []*egv1a1.ClientTrafficPolicy {
1514-
copies := make([]*egv1a1.ClientTrafficPolicy, len(policies))
1515-
for i, p := range policies {
1516-
out := *p
1517-
p.Status.DeepCopyInto(&out.Status)
1518-
copies[i] = &out
1519-
}
1520-
return copies
1521-
}

internal/gatewayapi/envoyextensionpolicy.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
103103
// The routes are grouped by sectionNames of their targetRefs.
104104
gatewayRouteMap := make(map[string]map[string]sets.Set[string])
105105

106-
policyCopies := envoyExtensionPolicyCopiesWithStatusDeepCopy(envoyExtensionPolicies)
107-
108106
handledPolicies := make(map[types.NamespacedName]*egv1a1.EnvoyExtensionPolicy)
109107

110108
// Translate
@@ -122,7 +120,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
122120
if isRouteRule(currTarget) {
123121
policy, found := handledPolicies[policyName]
124122
if !found {
125-
policy = policyCopies[i]
123+
policy = envoyExtensionPolicies[i]
126124
res = append(res, policy)
127125
handledPolicies[policyName] = policy
128126
}
@@ -148,7 +146,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
148146
if isRoute(currTarget) {
149147
policy, found := handledPolicies[policyName]
150148
if !found {
151-
policy = policyCopies[i]
149+
policy = envoyExtensionPolicies[i]
152150
res = append(res, policy)
153151
handledPolicies[policyName] = policy
154152
}
@@ -168,7 +166,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
168166
if isListener(currTarget) {
169167
policy, found := handledPolicies[policyName]
170168
if !found {
171-
policy = policyCopies[i]
169+
policy = envoyExtensionPolicies[i]
172170
res = append(res, policy)
173171
handledPolicies[policyName] = policy
174172
}
@@ -194,7 +192,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
194192
if isGateway(currTarget) {
195193
policy, found := handledPolicies[policyName]
196194
if !found {
197-
policy = policyCopies[i]
195+
policy = envoyExtensionPolicies[i]
198196
res = append(res, policy)
199197
handledPolicies[policyName] = policy
200198
}
@@ -1282,15 +1280,3 @@ func (t *Translator) buildDynamicModules(
12821280

12831281
return dmIRList, errs
12841282
}
1285-
1286-
// envoyExtensionPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
1287-
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
1288-
func envoyExtensionPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyExtensionPolicy) []*egv1a1.EnvoyExtensionPolicy {
1289-
copies := make([]*egv1a1.EnvoyExtensionPolicy, len(policies))
1290-
for i, p := range policies {
1291-
out := *p
1292-
p.Status.DeepCopyInto(&out.Status)
1293-
copies[i] = &out
1294-
}
1295-
return copies
1296-
}

internal/gatewayapi/envoypatchpolicy.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ import (
1919

2020
func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.EnvoyPatchPolicy, xdsIR resource.XdsIRMap) []*egv1a1.EnvoyPatchPolicy {
2121
// EnvoyPatchPolicies are already sorted by the provider layer (priority, then timestamp, then name)
22-
policyCopies := envoyPatchPolicyCopiesWithStatusDeepCopy(envoyPatchPolicies)
2322
res := make([]*egv1a1.EnvoyPatchPolicy, 0, len(envoyPatchPolicies))
2423

2524
for i := range envoyPatchPolicies {
26-
policy := policyCopies[i]
25+
policy := envoyPatchPolicies[i]
2726
res = append(res, policy)
2827
var (
2928
ancestorRef gwapiv1.ParentReference
@@ -146,15 +145,3 @@ func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.Envo
146145

147146
return res
148147
}
149-
150-
// envoyPatchPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
151-
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
152-
func envoyPatchPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyPatchPolicy) []*egv1a1.EnvoyPatchPolicy {
153-
copies := make([]*egv1a1.EnvoyPatchPolicy, len(policies))
154-
for i, p := range policies {
155-
out := *p
156-
p.Status.DeepCopyInto(&out.Status)
157-
copies[i] = &out
158-
}
159-
return copies
160-
}

internal/gatewayapi/extensionserverpolicy.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12-
"maps"
1312
"strings"
1413

1514
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16-
"k8s.io/apimachinery/pkg/runtime"
1715
"k8s.io/apimachinery/pkg/types"
1816
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
1917

@@ -38,13 +36,11 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst
3836
gatewayMap[key] = &policyGatewayTargetContext{GatewayContext: gw}
3937
}
4038

41-
policyCopies := extensionServerPolicyCopiesWithStatusDeepCopy(policies)
42-
4339
var errs error
4440
// Process the policies targeting Gateways. Only update the policy status if it was accepted.
4541
// A policy is considered accepted if at least one targetRef contained inside matched a listener.
4642
for i := range policies {
47-
policy := policyCopies[i]
43+
policy := policies[i]
4844
var policyStatus gwapiv1.PolicyStatus
4945
accepted := false
5046
targetRefs, err := extractTargetRefs(&policy, gateways)
@@ -192,17 +188,3 @@ func (t *Translator) translateExtServerPolicyForGateway(
192188
}
193189
return found
194190
}
195-
196-
// extensionServerPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied status entries.
197-
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
198-
func extensionServerPolicyCopiesWithStatusDeepCopy(policies []unstructured.Unstructured) []unstructured.Unstructured {
199-
copies := make([]unstructured.Unstructured, len(policies))
200-
for i, p := range policies {
201-
p.Object = maps.Clone(p.Object) // shallow copy map - no shared ref for "status" key
202-
if statusObj, ok := policies[i].Object["status"].(map[string]any); ok {
203-
p.Object["status"] = runtime.DeepCopyJSON(statusObj)
204-
}
205-
copies[i] = p
206-
}
207-
return copies
208-
}

internal/gatewayapi/resource/resource.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ package resource
77

88
import (
99
"context"
10+
"maps"
1011
"reflect"
1112
"sort"
1213

1314
certificatesv1b1 "k8s.io/api/certificates/v1beta1"
1415
corev1 "k8s.io/api/core/v1"
1516
discoveryv1 "k8s.io/api/discovery/v1"
1617
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18+
"k8s.io/apimachinery/pkg/runtime"
1719
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
1820
gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
1921
mcsapiv1a1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
@@ -502,3 +504,83 @@ func (r *Resources) Sort() {
502504
return tsI.Before(&tsJ)
503505
})
504506
}
507+
508+
// StatusDeepCopy returns a shallow copy of Resources in which every status-bearing
509+
// object is itself shallow-copied with only its Status field deep-copied.
510+
//
511+
// The translator mutates resource Status in place while building status updates,
512+
// but the input tree is shared with the watchable coalesce goroutine, which walks it
513+
// with reflect.DeepEqual. Deep-copying only the Status fields isolates those in-place
514+
// mutations from the shared objects without the memory cost of a full DeepCopy of the
515+
// (much larger, immutable-during-translation) Spec.
516+
func (r *Resources) StatusDeepCopy() *Resources {
517+
if r == nil {
518+
return nil
519+
}
520+
521+
// Shallow copy of the struct: all fields (including the shared, non-status-bearing
522+
// slices such as Namespaces, Services, Secrets, ...) are carried over as-is.
523+
out := *r
524+
525+
out.GatewayClass = statusDeepCopyObject(r.GatewayClass, func(dst, src *gwapiv1.GatewayClass) { src.Status.DeepCopyInto(&dst.Status) })
526+
out.EnvoyProxyForGatewayClass = statusDeepCopyObject(r.EnvoyProxyForGatewayClass, func(dst, src *egv1a1.EnvoyProxy) { src.Status.DeepCopyInto(&dst.Status) })
527+
out.EnvoyProxiesForGateways = statusDeepCopySlice(r.EnvoyProxiesForGateways, func(dst, src *egv1a1.EnvoyProxy) { src.Status.DeepCopyInto(&dst.Status) })
528+
out.Gateways = statusDeepCopySlice(r.Gateways, func(dst, src *gwapiv1.Gateway) { src.Status.DeepCopyInto(&dst.Status) })
529+
out.ListenerSets = statusDeepCopySlice(r.ListenerSets, func(dst, src *gwapiv1.ListenerSet) { src.Status.DeepCopyInto(&dst.Status) })
530+
out.HTTPRoutes = statusDeepCopySlice(r.HTTPRoutes, func(dst, src *gwapiv1.HTTPRoute) { src.Status.DeepCopyInto(&dst.Status) })
531+
out.GRPCRoutes = statusDeepCopySlice(r.GRPCRoutes, func(dst, src *gwapiv1.GRPCRoute) { src.Status.DeepCopyInto(&dst.Status) })
532+
out.TLSRoutes = statusDeepCopySlice(r.TLSRoutes, func(dst, src *gwapiv1.TLSRoute) { src.Status.DeepCopyInto(&dst.Status) })
533+
out.TCPRoutes = statusDeepCopySlice(r.TCPRoutes, func(dst, src *gwapiv1.TCPRoute) { src.Status.DeepCopyInto(&dst.Status) })
534+
out.UDPRoutes = statusDeepCopySlice(r.UDPRoutes, func(dst, src *gwapiv1.UDPRoute) { src.Status.DeepCopyInto(&dst.Status) })
535+
out.EnvoyPatchPolicies = statusDeepCopySlice(r.EnvoyPatchPolicies, func(dst, src *egv1a1.EnvoyPatchPolicy) { src.Status.DeepCopyInto(&dst.Status) })
536+
out.ClientTrafficPolicies = statusDeepCopySlice(r.ClientTrafficPolicies, func(dst, src *egv1a1.ClientTrafficPolicy) { src.Status.DeepCopyInto(&dst.Status) })
537+
out.BackendTrafficPolicies = statusDeepCopySlice(r.BackendTrafficPolicies, func(dst, src *egv1a1.BackendTrafficPolicy) { src.Status.DeepCopyInto(&dst.Status) })
538+
out.SecurityPolicies = statusDeepCopySlice(r.SecurityPolicies, func(dst, src *egv1a1.SecurityPolicy) { src.Status.DeepCopyInto(&dst.Status) })
539+
out.BackendTLSPolicies = statusDeepCopySlice(r.BackendTLSPolicies, func(dst, src *gwapiv1.BackendTLSPolicy) { src.Status.DeepCopyInto(&dst.Status) })
540+
out.EnvoyExtensionPolicies = statusDeepCopySlice(r.EnvoyExtensionPolicies, func(dst, src *egv1a1.EnvoyExtensionPolicy) { src.Status.DeepCopyInto(&dst.Status) })
541+
out.Backends = statusDeepCopySlice(r.Backends, func(dst, src *egv1a1.Backend) { src.Status.DeepCopyInto(&dst.Status) })
542+
out.ExtensionServerPolicies = statusDeepCopyUnstructured(r.ExtensionServerPolicies)
543+
544+
return &out
545+
}
546+
547+
// statusDeepCopyObject returns a shallow copy of obj with copyStatus applied to
548+
// deep-copy its Status field, or nil if obj is nil.
549+
func statusDeepCopyObject[T any](obj *T, copyStatus func(dst, src *T)) *T {
550+
if obj == nil {
551+
return nil
552+
}
553+
cp := *obj
554+
copyStatus(&cp, obj)
555+
return &cp
556+
}
557+
558+
// statusDeepCopySlice returns a new slice of shallow copies, each with copyStatus
559+
// applied to deep-copy its Status field, preserving the original length and nil-ness.
560+
func statusDeepCopySlice[T any](in []*T, copyStatus func(dst, src *T)) []*T {
561+
if in == nil {
562+
return nil
563+
}
564+
out := make([]*T, len(in))
565+
for i, obj := range in {
566+
out[i] = statusDeepCopyObject(obj, copyStatus)
567+
}
568+
return out
569+
}
570+
571+
// statusDeepCopyUnstructured returns shallow copies with deep-copied status entries
572+
// for unstructured resources (ExtensionServerPolicies).
573+
func statusDeepCopyUnstructured(in []unstructured.Unstructured) []unstructured.Unstructured {
574+
if in == nil {
575+
return nil
576+
}
577+
out := make([]unstructured.Unstructured, len(in))
578+
for i, p := range in {
579+
p.Object = maps.Clone(p.Object) // shallow copy map - no shared ref for "status" key
580+
if statusObj, ok := in[i].Object["status"].(map[string]any); ok {
581+
p.Object["status"] = runtime.DeepCopyJSON(statusObj)
582+
}
583+
out[i] = p
584+
}
585+
return out
586+
}

0 commit comments

Comments
 (0)