Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion internal/cmd/egctl/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,10 @@ func translateGatewayAPIToGatewayAPI(resources *resource.Resources) (resource.Re
msg := fmt.Sprintf("%s: %v", status.MsgGatewayClassInvalidParams, err)
status.SetGatewayClassAccepted(resources.GatewayClass, false, string(gwapiv1.GatewayClassReasonInvalidParameters), msg)
}
gRes.EnvoyProxyForGatewayClass = resources.EnvoyProxyForGatewayClass
// Don't overwrite gRes.EnvoyProxyForGatewayClass here: the translator already
// populated it with the status-isolated copy carrying the computed Accepted
// condition. resources.EnvoyProxyForGatewayClass is the original object, whose
// Status the translator no longer mutates in place (see Translate's StatusDeepCopy).
}

if !epInvalid {
Expand Down
16 changes: 1 addition & 15 deletions internal/gatewayapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import (
)

func (t *Translator) ProcessBackends(backends []*egv1a1.Backend, backendTLSPolicies []*gwapiv1.BackendTLSPolicy) []*egv1a1.Backend {
backendCopies := backendCopiesWithStatusDeepCopy(backends)
res := make([]*egv1a1.Backend, 0, len(backends))
for i := range backends {
backend := backendCopies[i]
for _, backend := range backends {
// Ensure Backends are enabled
if !t.BackendEnabled {
status.UpdateBackendStatusAcceptedCondition(backend, false,
Expand Down Expand Up @@ -206,15 +204,3 @@ func validateIP(epIP *egv1a1.IPEndpoint, runningOnHost bool) status.Error {
}
return nil
}

// backendCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
func backendCopiesWithStatusDeepCopy(backends []*egv1a1.Backend) []*egv1a1.Backend {
copies := make([]*egv1a1.Backend, len(backends))
for i, b := range backends {
out := *b
b.Status.DeepCopyInto(&out.Status)
copies[i] = &out
}
return copies
}
22 changes: 4 additions & 18 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,6 @@ func (t *Translator) ProcessBackendTrafficPolicies(
SectionIndex: make(map[types.NamespacedName]sets.Set[string], gatewayMapSize),
}

policyCopies := backendTrafficPolicyCopiesWithStatusDeepCopy(backendTrafficPolicies)

handledPolicies := make(map[types.NamespacedName]*egv1a1.BackendTrafficPolicy, policyMapSize)

// Translate
Expand All @@ -281,7 +279,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
if isRouteRule(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = backendTrafficPolicies[i]
handledPolicies[policyName] = policy
res = append(res, policy)
}
Expand All @@ -307,7 +305,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
if isRoute(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = backendTrafficPolicies[i]
handledPolicies[policyName] = policy
res = append(res, policy)
}
Expand All @@ -327,7 +325,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
if isListener(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = backendTrafficPolicies[i]
handledPolicies[policyName] = policy
res = append(res, policy)
}
Expand All @@ -352,7 +350,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(
if isGateway(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = backendTrafficPolicies[i]
handledPolicies[policyName] = policy
res = append(res, policy)
}
Expand Down Expand Up @@ -2196,15 +2194,3 @@ func buildRouteStatName(routeStatName string, metadata *ir.ResourceMetadata) *st

return &statName
}

// backendTrafficPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
func backendTrafficPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.BackendTrafficPolicy) []*egv1a1.BackendTrafficPolicy {
copies := make([]*egv1a1.BackendTrafficPolicy, len(policies))
for i, p := range policies {
out := *p
p.Status.DeepCopyInto(&out.Status)
copies[i] = &out
}
return copies
}
18 changes: 2 additions & 16 deletions internal/gatewayapi/clienttrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ func (t *Translator) ProcessClientTrafficPolicies(
listenerSetMap[key] = ls
}

policyCopies := clientTrafficPolicyCopiesWithStatusDeepCopy(clientTrafficPolicies)

handledPolicies := make(map[types.NamespacedName]*egv1a1.ClientTrafficPolicy)
// Translate
// 1. First translate Policies with a sectionName set
Expand All @@ -112,7 +110,7 @@ func (t *Translator) ProcessClientTrafficPolicies(
if hasSectionName(&targetRef) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = clientTrafficPolicies[i]
handledPolicies[policyName] = policy
res = append(res, policy)
}
Expand Down Expand Up @@ -258,7 +256,7 @@ func (t *Translator) ProcessClientTrafficPolicies(

policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = clientTrafficPolicies[i]
res = append(res, policy)
handledPolicies[policyName] = policy
}
Expand Down Expand Up @@ -1510,15 +1508,3 @@ func translateHeaderModifier(headerModifier *egv1a1.HTTPHeaderFilter, modType st

return addRequestHeaders, removeRequestHeaders, removeRequestHeadersOnMatch, errs
}

// clientTrafficPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
func clientTrafficPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.ClientTrafficPolicy) []*egv1a1.ClientTrafficPolicy {
copies := make([]*egv1a1.ClientTrafficPolicy, len(policies))
for i, p := range policies {
out := *p
p.Status.DeepCopyInto(&out.Status)
copies[i] = &out
}
return copies
}
22 changes: 4 additions & 18 deletions internal/gatewayapi/envoyextensionpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
// The routes are grouped by sectionNames of their targetRefs.
gatewayRouteMap := make(map[string]map[string]sets.Set[string])

policyCopies := envoyExtensionPolicyCopiesWithStatusDeepCopy(envoyExtensionPolicies)

handledPolicies := make(map[types.NamespacedName]*egv1a1.EnvoyExtensionPolicy)

// Translate
Expand All @@ -122,7 +120,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
if isRouteRule(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = envoyExtensionPolicies[i]
res = append(res, policy)
handledPolicies[policyName] = policy
}
Expand All @@ -148,7 +146,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
if isRoute(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = envoyExtensionPolicies[i]
res = append(res, policy)
handledPolicies[policyName] = policy
}
Expand All @@ -168,7 +166,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
if isListener(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = envoyExtensionPolicies[i]
res = append(res, policy)
handledPolicies[policyName] = policy
}
Expand All @@ -194,7 +192,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies(
if isGateway(currTarget) {
policy, found := handledPolicies[policyName]
if !found {
policy = policyCopies[i]
policy = envoyExtensionPolicies[i]
res = append(res, policy)
handledPolicies[policyName] = policy
}
Expand Down Expand Up @@ -1282,15 +1280,3 @@ func (t *Translator) buildDynamicModules(

return dmIRList, errs
}

// envoyExtensionPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
func envoyExtensionPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyExtensionPolicy) []*egv1a1.EnvoyExtensionPolicy {
copies := make([]*egv1a1.EnvoyExtensionPolicy, len(policies))
for i, p := range policies {
out := *p
p.Status.DeepCopyInto(&out.Status)
copies[i] = &out
}
return copies
}
15 changes: 1 addition & 14 deletions internal/gatewayapi/envoypatchpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ import (

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

for i := range envoyPatchPolicies {
policy := policyCopies[i]
policy := envoyPatchPolicies[i]
res = append(res, policy)
var (
ancestorRef gwapiv1.ParentReference
Expand Down Expand Up @@ -146,15 +145,3 @@ func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.Envo

return res
}

// envoyPatchPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields.
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
func envoyPatchPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyPatchPolicy) []*egv1a1.EnvoyPatchPolicy {
copies := make([]*egv1a1.EnvoyPatchPolicy, len(policies))
for i, p := range policies {
out := *p
p.Status.DeepCopyInto(&out.Status)
copies[i] = &out
}
return copies
}
20 changes: 1 addition & 19 deletions internal/gatewayapi/extensionserverpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"

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

policyCopies := extensionServerPolicyCopiesWithStatusDeepCopy(policies)

var errs error
// Process the policies targeting Gateways. Only update the policy status if it was accepted.
// A policy is considered accepted if at least one targetRef contained inside matched a listener.
for i := range policies {
policy := policyCopies[i]
policy := policies[i]
var policyStatus gwapiv1.PolicyStatus
accepted := false
targetRefs, err := extractTargetRefs(&policy, gateways)
Expand Down Expand Up @@ -192,17 +188,3 @@ func (t *Translator) translateExtServerPolicyForGateway(
}
return found
}

// extensionServerPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied status entries.
// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine.
func extensionServerPolicyCopiesWithStatusDeepCopy(policies []unstructured.Unstructured) []unstructured.Unstructured {
copies := make([]unstructured.Unstructured, len(policies))
for i, p := range policies {
p.Object = maps.Clone(p.Object) // shallow copy map - no shared ref for "status" key
if statusObj, ok := policies[i].Object["status"].(map[string]any); ok {
p.Object["status"] = runtime.DeepCopyJSON(statusObj)
}
copies[i] = p
}
return copies
}
82 changes: 82 additions & 0 deletions internal/gatewayapi/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ package resource

import (
"context"
"maps"
"reflect"
"sort"

certificatesv1b1 "k8s.io/api/certificates/v1beta1"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
mcsapiv1a1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
Expand Down Expand Up @@ -502,3 +504,83 @@ func (r *Resources) Sort() {
return tsI.Before(&tsJ)
})
}

// StatusDeepCopy returns a shallow copy of Resources in which every status-bearing
// object is itself shallow-copied with only its Status field deep-copied.
//
// The translator mutates resource Status in place while building status updates,
// but the input tree is shared with the watchable coalesce goroutine, which walks it
// with reflect.DeepEqual. Deep-copying only the Status fields isolates those in-place
// mutations from the shared objects without the memory cost of a full DeepCopy of the
// (much larger, immutable-during-translation) Spec.
func (r *Resources) StatusDeepCopy() *Resources {
if r == nil {
return nil
}

// Shallow copy of the struct: all fields (including the shared, non-status-bearing
// slices such as Namespaces, Services, Secrets, ...) are carried over as-is.
out := *r

out.GatewayClass = statusDeepCopyObject(r.GatewayClass, func(dst, src *gwapiv1.GatewayClass) { src.Status.DeepCopyInto(&dst.Status) })
out.EnvoyProxyForGatewayClass = statusDeepCopyObject(r.EnvoyProxyForGatewayClass, func(dst, src *egv1a1.EnvoyProxy) { src.Status.DeepCopyInto(&dst.Status) })
out.EnvoyProxiesForGateways = statusDeepCopySlice(r.EnvoyProxiesForGateways, func(dst, src *egv1a1.EnvoyProxy) { src.Status.DeepCopyInto(&dst.Status) })
out.Gateways = statusDeepCopySlice(r.Gateways, func(dst, src *gwapiv1.Gateway) { src.Status.DeepCopyInto(&dst.Status) })
out.ListenerSets = statusDeepCopySlice(r.ListenerSets, func(dst, src *gwapiv1.ListenerSet) { src.Status.DeepCopyInto(&dst.Status) })
out.HTTPRoutes = statusDeepCopySlice(r.HTTPRoutes, func(dst, src *gwapiv1.HTTPRoute) { src.Status.DeepCopyInto(&dst.Status) })
out.GRPCRoutes = statusDeepCopySlice(r.GRPCRoutes, func(dst, src *gwapiv1.GRPCRoute) { src.Status.DeepCopyInto(&dst.Status) })
out.TLSRoutes = statusDeepCopySlice(r.TLSRoutes, func(dst, src *gwapiv1.TLSRoute) { src.Status.DeepCopyInto(&dst.Status) })
out.TCPRoutes = statusDeepCopySlice(r.TCPRoutes, func(dst, src *gwapiv1.TCPRoute) { src.Status.DeepCopyInto(&dst.Status) })
out.UDPRoutes = statusDeepCopySlice(r.UDPRoutes, func(dst, src *gwapiv1.UDPRoute) { src.Status.DeepCopyInto(&dst.Status) })
out.EnvoyPatchPolicies = statusDeepCopySlice(r.EnvoyPatchPolicies, func(dst, src *egv1a1.EnvoyPatchPolicy) { src.Status.DeepCopyInto(&dst.Status) })
out.ClientTrafficPolicies = statusDeepCopySlice(r.ClientTrafficPolicies, func(dst, src *egv1a1.ClientTrafficPolicy) { src.Status.DeepCopyInto(&dst.Status) })
out.BackendTrafficPolicies = statusDeepCopySlice(r.BackendTrafficPolicies, func(dst, src *egv1a1.BackendTrafficPolicy) { src.Status.DeepCopyInto(&dst.Status) })
out.SecurityPolicies = statusDeepCopySlice(r.SecurityPolicies, func(dst, src *egv1a1.SecurityPolicy) { src.Status.DeepCopyInto(&dst.Status) })
out.BackendTLSPolicies = statusDeepCopySlice(r.BackendTLSPolicies, func(dst, src *gwapiv1.BackendTLSPolicy) { src.Status.DeepCopyInto(&dst.Status) })
out.EnvoyExtensionPolicies = statusDeepCopySlice(r.EnvoyExtensionPolicies, func(dst, src *egv1a1.EnvoyExtensionPolicy) { src.Status.DeepCopyInto(&dst.Status) })
out.Backends = statusDeepCopySlice(r.Backends, func(dst, src *egv1a1.Backend) { src.Status.DeepCopyInto(&dst.Status) })
out.ExtensionServerPolicies = statusDeepCopyUnstructured(r.ExtensionServerPolicies)

return &out
}

// statusDeepCopyObject returns a shallow copy of obj with copyStatus applied to
// deep-copy its Status field, or nil if obj is nil.
func statusDeepCopyObject[T any](obj *T, copyStatus func(dst, src *T)) *T {
if obj == nil {
return nil
}
cp := *obj
copyStatus(&cp, obj)
return &cp
}

// statusDeepCopySlice returns a new slice of shallow copies, each with copyStatus
// applied to deep-copy its Status field, preserving the original length and nil-ness.
func statusDeepCopySlice[T any](in []*T, copyStatus func(dst, src *T)) []*T {
if in == nil {
return nil
}
out := make([]*T, len(in))
for i, obj := range in {
out[i] = statusDeepCopyObject(obj, copyStatus)
}
return out
}

// statusDeepCopyUnstructured returns shallow copies with deep-copied status entries
// for unstructured resources (ExtensionServerPolicies).
func statusDeepCopyUnstructured(in []unstructured.Unstructured) []unstructured.Unstructured {
if in == nil {
return nil
}
out := make([]unstructured.Unstructured, len(in))
for i, p := range in {
p.Object = maps.Clone(p.Object) // shallow copy map - no shared ref for "status" key
if statusObj, ok := in[i].Object["status"].(map[string]any); ok {
p.Object["status"] = runtime.DeepCopyJSON(statusObj)
}
out[i] = p
}
return out
}
Loading