Skip to content
Closed
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
52 changes: 49 additions & 3 deletions internal/provider/kubernetes/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,12 +501,34 @@

// mergeRouteParentStatus merges the old and new RouteParentStatus.
// This is needed because the RouteParentStatus doesn't support strategic merge patch yet.
// It also removes any status.parents entries that are no longer referenced in the route's spec.parentRefs.
func mergeRouteParentStatus(ns string, old, new []gwapiv1.RouteParentStatus) []gwapiv1.RouteParentStatus {
merged := make([]gwapiv1.RouteParentStatus, len(old))
_ = copy(merged, old)
// First, create a map of all parentRefs in the new status
// These represent the current valid parentRefs from spec.parentRefs
newParentRefs := make(map[string]bool)
for _, parent := range new {
// Create a unique key for each parentRef
key := parentRefToString(parent.ParentRef, ns)
newParentRefs[key] = true
}

// Filter out old entries that are no longer referenced in spec.parentRefs
var filteredOld []gwapiv1.RouteParentStatus
for _, existing := range old {
key := parentRefToString(existing.ParentRef, ns)
if newParentRefs[key] {
// Keep this entry as it's still referenced
filteredOld = append(filteredOld, existing)
}
// Skip entries that are no longer referenced
}

// Now merge the filtered old entries with the new ones
merged := make([]gwapiv1.RouteParentStatus, len(filteredOld))
_ = copy(merged, filteredOld)
for _, parent := range new {
found := -1
for i, existing := range old {
for i, existing := range filteredOld {
if isParentRefEqual(parent.ParentRef, existing.ParentRef, ns) {
found = i
break
Expand All @@ -521,6 +543,30 @@
return merged
}

// parentRefToString creates a unique string representation of a ParentReference
func parentRefToString(ref gwapiv1.ParentReference, routeNS string) string {
defaultGroup := (*gwapiv1.Group)(&gwapiv1.GroupVersion.Group)
group := defaultGroup
if ref.Group != nil {
group = ref.Group
}

Check warning on line 552 in internal/provider/kubernetes/status.go

View check run for this annotation

Codecov / codecov/patch

internal/provider/kubernetes/status.go#L551-L552

Added lines #L551 - L552 were not covered by tests

defaultKind := gwapiv1.Kind(resource.KindGateway)
kind := &defaultKind
if ref.Kind != nil {
kind = ref.Kind
}

Check warning on line 558 in internal/provider/kubernetes/status.go

View check run for this annotation

Codecov / codecov/patch

internal/provider/kubernetes/status.go#L557-L558

Added lines #L557 - L558 were not covered by tests

// If the parent's namespace is not set, default to the namespace of the Route.
defaultNS := gwapiv1.Namespace(routeNS)
namespace := &defaultNS
if ref.Namespace != nil {
namespace = ref.Namespace
}

return fmt.Sprintf("%s/%s/%s/%s", *group, *kind, *namespace, ref.Name)
}

func isParentRefEqual(ref1, ref2 gwapiv1.ParentReference, routeNS string) bool {
defaultGroup := (*gwapiv1.Group)(&gwapiv1.GroupVersion.Group)
if ref1.Group == nil {
Expand Down
174 changes: 174 additions & 0 deletions internal/provider/kubernetes/status_cleanup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package kubernetes

import (
"reflect"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
)

func Test_mergeRouteParentStatusCleanup(t *testing.T) {
type args struct {
ns string
old []gwapiv1.RouteParentStatus
new []gwapiv1.RouteParentStatus
}
tests := []struct {
name string
args args
want []gwapiv1.RouteParentStatus
}{
{
name: "remove old entries that are no longer referenced",
args: args{
ns: "default",
old: []gwapiv1.RouteParentStatus{
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway1",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "Accepted",
},
},
},
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway2",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "OldReason",
},
},
},
},
new: []gwapiv1.RouteParentStatus{
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway1",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "Accepted",
},
},
},
// gateway2 is removed from spec.parentRefs, so it should not appear in the merged result
},
},
want: []gwapiv1.RouteParentStatus{
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway1",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "Accepted",
},
},
},
},
},
{
name: "update existing entry and keep only referenced entries",
args: args{
ns: "default",
old: []gwapiv1.RouteParentStatus{
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway1",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "OldReason",
},
},
},
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway2",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "OldReason",
},
},
},
},
new: []gwapiv1.RouteParentStatus{
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway1",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "NewReason",
},
},
},
// gateway2 is removed from spec.parentRefs, so it should not appear in the merged result
},
},
want: []gwapiv1.RouteParentStatus{
{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
ParentRef: gwapiv1.ParentReference{
Name: "gateway1",
Namespace: ptr.To[gwapiv1.Namespace]("default"),
},
Conditions: []metav1.Condition{
{
Type: string(gwapiv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: "NewReason",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := mergeRouteParentStatus(tt.args.ns, tt.args.old, tt.args.new); !reflect.DeepEqual(got, tt.want) {
t.Errorf("mergeRouteParentStatus() = %v, want %v", got, tt.want)
}
})
}
}
Loading
Loading