Skip to content

Commit ec7c527

Browse files
kate-osbornKate Osborn
authored and
Kate Osborn
committed
Fix duplicate ancestors on UpstreamSettingsPolicy status (#2937)
Problem: When an UpstreamSettingsPolicy targeted multiple Services, NGF would write duplicate ancestors to its status. Solution: Only add unique ancestors to policy ancestors list.
1 parent f2a765a commit ec7c527

File tree

4 files changed

+194
-1
lines changed

4 files changed

+194
-1
lines changed

Diff for: internal/mode/static/state/graph/policies.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,18 @@ func attachPolicyToService(
112112

113113
if !gw.Valid {
114114
ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("Parent Gateway is invalid")}
115+
if ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) {
116+
return
117+
}
118+
115119
policy.Ancestors = append(policy.Ancestors, ancestor)
116120
return
117121
}
118122

119-
policy.Ancestors = append(policy.Ancestors, ancestor)
123+
if !ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) {
124+
policy.Ancestors = append(policy.Ancestors, ancestor)
125+
}
126+
120127
svc.Policies = append(svc.Policies, policy)
121128
}
122129

Diff for: internal/mode/static/state/graph/policies_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ func TestAttachPolicyToService(t *testing.T) {
539539
t.Parallel()
540540

541541
gwNsname := types.NamespacedName{Namespace: testNs, Name: "gateway"}
542+
gw2Nsname := types.NamespacedName{Namespace: testNs, Name: "gateway2"}
542543

543544
getGateway := func(valid bool) *Gateway {
544545
return &Gateway{
@@ -572,6 +573,47 @@ func TestAttachPolicyToService(t *testing.T) {
572573
},
573574
},
574575
},
576+
{
577+
name: "attachment; ancestor already exists so don't duplicate",
578+
policy: &Policy{
579+
Source: &policiesfakes.FakePolicy{},
580+
Ancestors: []PolicyAncestor{
581+
{
582+
Ancestor: getGatewayParentRef(gwNsname),
583+
},
584+
},
585+
},
586+
svc: &ReferencedService{},
587+
gw: getGateway(true /*valid*/),
588+
expAttached: true,
589+
expAncestors: []PolicyAncestor{
590+
{
591+
Ancestor: getGatewayParentRef(gwNsname), // only one ancestor per Gateway
592+
},
593+
},
594+
},
595+
{
596+
name: "attachment; ancestor doesn't exists so add it",
597+
policy: &Policy{
598+
Source: &policiesfakes.FakePolicy{},
599+
Ancestors: []PolicyAncestor{
600+
{
601+
Ancestor: getGatewayParentRef(gw2Nsname),
602+
},
603+
},
604+
},
605+
svc: &ReferencedService{},
606+
gw: getGateway(true /*valid*/),
607+
expAttached: true,
608+
expAncestors: []PolicyAncestor{
609+
{
610+
Ancestor: getGatewayParentRef(gw2Nsname),
611+
},
612+
{
613+
Ancestor: getGatewayParentRef(gwNsname),
614+
},
615+
},
616+
},
575617
{
576618
name: "no attachment; gateway is invalid",
577619
policy: &Policy{Source: &policiesfakes.FakePolicy{}},

Diff for: internal/mode/static/state/graph/policy_ancestor.go

+34
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"k8s.io/apimachinery/pkg/types"
55
v1 "sigs.k8s.io/gateway-api/apis/v1"
66
"sigs.k8s.io/gateway-api/apis/v1alpha2"
7+
8+
"github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers"
79
)
810

911
const maxAncestors = 16
@@ -61,3 +63,35 @@ func createParentReference(
6163
Name: v1.ObjectName(nsname.Name),
6264
}
6365
}
66+
67+
func ancestorsContainsAncestorRef(ancestors []PolicyAncestor, ref v1.ParentReference) bool {
68+
for _, an := range ancestors {
69+
if parentRefEqual(an.Ancestor, ref) {
70+
return true
71+
}
72+
}
73+
74+
return false
75+
}
76+
77+
func parentRefEqual(ref1, ref2 v1.ParentReference) bool {
78+
if !helpers.EqualPointers(ref1.Kind, ref2.Kind) {
79+
return false
80+
}
81+
82+
if !helpers.EqualPointers(ref1.Group, ref2.Group) {
83+
return false
84+
}
85+
86+
if !helpers.EqualPointers(ref1.Namespace, ref2.Namespace) {
87+
return false
88+
}
89+
90+
// we don't check the other fields in ParentRef because we don't set them
91+
92+
if ref1.Name != ref2.Name {
93+
return false
94+
}
95+
96+
return true
97+
}

Diff for: internal/mode/static/state/graph/policy_ancestor_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"testing"
55

66
. "github.com/onsi/gomega"
7+
"k8s.io/apimachinery/pkg/types"
78
v1 "sigs.k8s.io/gateway-api/apis/v1"
89
"sigs.k8s.io/gateway-api/apis/v1alpha2"
910

1011
ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1"
12+
"github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds"
1113
)
1214

1315
func TestBackendTLSPolicyAncestorsFull(t *testing.T) {
@@ -169,3 +171,111 @@ func TestNGFPolicyAncestorsFull(t *testing.T) {
169171
})
170172
}
171173
}
174+
175+
func TestAncestorContainsAncestorRef(t *testing.T) {
176+
t.Parallel()
177+
178+
gw1 := types.NamespacedName{Namespace: testNs, Name: "gw1"}
179+
gw2 := types.NamespacedName{Namespace: testNs, Name: "gw2"}
180+
route := types.NamespacedName{Namespace: testNs, Name: "route"}
181+
newRoute := types.NamespacedName{Namespace: testNs, Name: "new-route"}
182+
183+
ancestors := []PolicyAncestor{
184+
{
185+
Ancestor: createParentReference(v1.GroupName, kinds.Gateway, gw1),
186+
},
187+
{
188+
Ancestor: createParentReference(v1.GroupName, kinds.Gateway, gw2),
189+
},
190+
{
191+
Ancestor: createParentReference(v1.GroupName, kinds.HTTPRoute, route),
192+
},
193+
}
194+
195+
tests := []struct {
196+
ref v1.ParentReference
197+
name string
198+
contains bool
199+
}{
200+
{
201+
name: "contains Gateway ref",
202+
ref: createParentReference(v1.GroupName, kinds.Gateway, gw1),
203+
contains: true,
204+
},
205+
{
206+
name: "contains Route ref",
207+
ref: createParentReference(v1.GroupName, kinds.HTTPRoute, route),
208+
contains: true,
209+
},
210+
{
211+
name: "does not contain ref",
212+
ref: createParentReference(v1.GroupName, kinds.HTTPRoute, newRoute),
213+
contains: false,
214+
},
215+
}
216+
217+
for _, test := range tests {
218+
t.Run(test.name, func(t *testing.T) {
219+
t.Parallel()
220+
g := NewWithT(t)
221+
222+
g.Expect(ancestorsContainsAncestorRef(ancestors, test.ref)).To(Equal(test.contains))
223+
})
224+
}
225+
}
226+
227+
func TestParentRefEqual(t *testing.T) {
228+
t.Parallel()
229+
ref1NsName := types.NamespacedName{Namespace: testNs, Name: "ref1"}
230+
231+
ref1 := createParentReference(v1.GroupName, kinds.HTTPRoute, ref1NsName)
232+
233+
tests := []struct {
234+
ref v1.ParentReference
235+
name string
236+
equal bool
237+
}{
238+
{
239+
name: "kinds different",
240+
ref: createParentReference(v1.GroupName, kinds.Gateway, ref1NsName),
241+
equal: false,
242+
},
243+
{
244+
name: "groups different",
245+
ref: createParentReference("diff-group", kinds.HTTPRoute, ref1NsName),
246+
equal: false,
247+
},
248+
{
249+
name: "namespace different",
250+
ref: createParentReference(
251+
v1.GroupName,
252+
kinds.HTTPRoute,
253+
types.NamespacedName{Namespace: "diff-ns", Name: "ref1"},
254+
),
255+
equal: false,
256+
},
257+
{
258+
name: "name different",
259+
ref: createParentReference(
260+
v1.GroupName,
261+
kinds.HTTPRoute,
262+
types.NamespacedName{Namespace: testNs, Name: "diff-name"},
263+
),
264+
equal: false,
265+
},
266+
{
267+
name: "equal",
268+
ref: createParentReference(v1.GroupName, kinds.HTTPRoute, ref1NsName),
269+
equal: true,
270+
},
271+
}
272+
273+
for _, test := range tests {
274+
t.Run(test.name, func(t *testing.T) {
275+
t.Parallel()
276+
g := NewWithT(t)
277+
278+
g.Expect(parentRefEqual(ref1, test.ref)).To(Equal(test.equal))
279+
})
280+
}
281+
}

0 commit comments

Comments
 (0)