Skip to content

Commit cc3c907

Browse files
authored
Add fix to route parentRef internal logic (#3418)
Internally generate ParentRefs for each listener in the Gateway if a route does not specify a sectionName. Problem: When a route does not specify a sectionName, it attaches to all attachable listeners on a Gateway. This is a problem internally when we try to bind the route to the listeners where duplicated hostnames between listeners can create AcceptedHostname conflicts and multiple ports on the listeners can overwrite the refStatus.ListenerPort. Solution: When a route does not specify a sectionName, generate a ParentRef for each listener in the Gateway. * Remove duplicate parentRefs from httproute status * Add some function comments * Fix conformance test * Revert parentRefIndex
1 parent ae9c118 commit cc3c907

File tree

6 files changed

+337
-20
lines changed

6 files changed

+337
-20
lines changed

internal/controller/state/graph/graph_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,32 @@ func TestBuildGraph(t *testing.T) {
772772
Attachable: true,
773773
Source: tr,
774774
ParentRefs: []ParentRef{
775+
{
776+
Idx: 0,
777+
Gateway: &ParentRefGateway{
778+
NamespacedName: client.ObjectKeyFromObject(gw1.Source),
779+
EffectiveNginxProxy: np1Effective,
780+
},
781+
Attachment: &ParentRefAttachmentStatus{
782+
AcceptedHostnames: map[string][]string{},
783+
Attached: false,
784+
FailedConditions: []conditions.Condition{conditions.NewRouteNotAllowedByListeners()},
785+
},
786+
SectionName: &gw1.Source.Spec.Listeners[0].Name,
787+
},
788+
{
789+
Idx: 0,
790+
Gateway: &ParentRefGateway{
791+
NamespacedName: client.ObjectKeyFromObject(gw1.Source),
792+
EffectiveNginxProxy: np1Effective,
793+
},
794+
Attachment: &ParentRefAttachmentStatus{
795+
AcceptedHostnames: map[string][]string{},
796+
Attached: false,
797+
FailedConditions: []conditions.Condition{conditions.NewRouteNotAllowedByListeners()},
798+
},
799+
SectionName: &gw1.Source.Spec.Listeners[1].Name,
800+
},
775801
{
776802
Idx: 0,
777803
Gateway: &ParentRefGateway{
@@ -785,12 +811,26 @@ func TestBuildGraph(t *testing.T) {
785811
client.ObjectKeyFromObject(gw1.Source),
786812
"listener-443-2",
787813
): {"fizz.example.org"},
814+
},
815+
},
816+
SectionName: &gw1.Source.Spec.Listeners[2].Name,
817+
},
818+
{
819+
Idx: 0,
820+
Gateway: &ParentRefGateway{
821+
NamespacedName: client.ObjectKeyFromObject(gw1.Source),
822+
EffectiveNginxProxy: np1Effective,
823+
},
824+
Attachment: &ParentRefAttachmentStatus{
825+
Attached: true,
826+
AcceptedHostnames: map[string][]string{
788827
CreateGatewayListenerKey(
789828
client.ObjectKeyFromObject(gw1.Source),
790829
"listener-8443",
791830
): {"fizz.example.org"},
792831
},
793832
},
833+
SectionName: &gw1.Source.Spec.Listeners[3].Name,
794834
},
795835
},
796836
Spec: L4RouteSpec{
@@ -814,6 +854,45 @@ func TestBuildGraph(t *testing.T) {
814854
Attachable: true,
815855
Source: tr2,
816856
ParentRefs: []ParentRef{
857+
{
858+
Idx: 0,
859+
Gateway: &ParentRefGateway{
860+
NamespacedName: client.ObjectKeyFromObject(gw1.Source),
861+
EffectiveNginxProxy: np1Effective,
862+
},
863+
Attachment: &ParentRefAttachmentStatus{
864+
Attached: false,
865+
AcceptedHostnames: map[string][]string{},
866+
FailedConditions: []conditions.Condition{conditions.NewRouteNotAllowedByListeners()},
867+
},
868+
SectionName: &gw1.Source.Spec.Listeners[0].Name,
869+
},
870+
{
871+
Idx: 0,
872+
Gateway: &ParentRefGateway{
873+
NamespacedName: client.ObjectKeyFromObject(gw1.Source),
874+
EffectiveNginxProxy: np1Effective,
875+
},
876+
Attachment: &ParentRefAttachmentStatus{
877+
AcceptedHostnames: map[string][]string{},
878+
Attached: false,
879+
FailedConditions: []conditions.Condition{conditions.NewRouteNotAllowedByListeners()},
880+
},
881+
SectionName: &gw1.Source.Spec.Listeners[1].Name,
882+
},
883+
{
884+
Idx: 0,
885+
Gateway: &ParentRefGateway{
886+
NamespacedName: client.ObjectKeyFromObject(gw1.Source),
887+
EffectiveNginxProxy: np1Effective,
888+
},
889+
Attachment: &ParentRefAttachmentStatus{
890+
Attached: false,
891+
AcceptedHostnames: map[string][]string{},
892+
FailedConditions: []conditions.Condition{conditions.NewRouteHostnameConflict()},
893+
},
894+
SectionName: &gw1.Source.Spec.Listeners[2].Name,
895+
},
817896
{
818897
Idx: 0,
819898
Gateway: &ParentRefGateway{
@@ -825,6 +904,7 @@ func TestBuildGraph(t *testing.T) {
825904
AcceptedHostnames: map[string][]string{},
826905
FailedConditions: []conditions.Condition{conditions.NewRouteHostnameConflict()},
827906
},
907+
SectionName: &gw1.Source.Spec.Listeners[3].Name,
828908
},
829909
},
830910
Spec: L4RouteSpec{

internal/controller/state/graph/policies_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,7 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) {
11261126
policyGVK := schema.GroupVersionKind{Group: "Group", Version: "Version", Kind: "MyPolicy"}
11271127
pol1, pol1Key := createTestPolicyAndKey(policyGVK, "pol1", hrRefCoffee)
11281128
pol2, pol2Key := createTestPolicyAndKey(policyGVK, "pol2", hrRefCoffee, hrRefCoffeeTea)
1129+
pol3, pol3Key := createTestPolicyAndKey(policyGVK, "pol3", hrRefCoffeeTea)
11291130

11301131
tests := []struct {
11311132
validator validation.PolicyValidator
@@ -1153,6 +1154,25 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) {
11531154
},
11541155
valid: true,
11551156
},
1157+
{
1158+
name: "no overlap two policies",
1159+
validator: &policiesfakes.FakeValidator{},
1160+
policies: map[PolicyKey]policies.Policy{
1161+
pol1Key: pol1,
1162+
pol3Key: pol3,
1163+
},
1164+
routes: map[RouteKey]*L7Route{
1165+
{
1166+
RouteType: RouteTypeHTTP,
1167+
NamespacedName: types.NamespacedName{Namespace: testNs, Name: "hr-coffee"},
1168+
}: createTestRouteWithPaths("hr-coffee", "/coffee"),
1169+
{
1170+
RouteType: RouteTypeHTTP,
1171+
NamespacedName: types.NamespacedName{Namespace: testNs, Name: "hr-coffee-tea"},
1172+
}: createTestRouteWithPaths("hr-coffee-tea", "/coffee-tea"),
1173+
},
1174+
valid: true,
1175+
},
11561176
{
11571177
name: "policy references route that overlaps a non-referenced route",
11581178
validator: &policiesfakes.FakeValidator{},
@@ -1256,7 +1276,7 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) {
12561276
g := NewWithT(t)
12571277

12581278
processed := processPolicies(test.policies, test.validator, test.routes, nil, gateways)
1259-
g.Expect(processed).To(HaveLen(1))
1279+
g.Expect(processed).To(HaveLen(len(test.policies)))
12601280

12611281
for _, pol := range processed {
12621282
g.Expect(pol.Valid).To(Equal(test.valid))

internal/controller/state/graph/route_common.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -277,35 +277,61 @@ func buildSectionNameRefs(
277277
routeNamespace string,
278278
gws map[types.NamespacedName]*Gateway,
279279
) ([]ParentRef, error) {
280-
sectionNameRefs := make([]ParentRef, 0, len(parentRefs))
281-
282280
type key struct {
283281
gwNsName types.NamespacedName
284282
sectionName string
285283
}
286284
uniqueSectionsPerGateway := make(map[key]struct{})
287285

286+
sectionNameRefs := make([]ParentRef, 0, len(parentRefs))
287+
288+
checkUniqueSections := func(key key) error {
289+
if _, exist := uniqueSectionsPerGateway[key]; exist {
290+
return fmt.Errorf("duplicate section name %q for Gateway %s", key.sectionName, key.gwNsName.String())
291+
}
292+
293+
uniqueSectionsPerGateway[key] = struct{}{}
294+
return nil
295+
}
296+
288297
for i, p := range parentRefs {
289298
gw := findGatewayForParentRef(p, routeNamespace, gws)
290299
if gw == nil {
291300
continue
292301
}
293302

294-
var sectionName string
295-
if p.SectionName != nil {
296-
sectionName = string(*p.SectionName)
297-
}
298-
299303
gwNsName := client.ObjectKeyFromObject(gw.Source)
300304
k := key{
301-
gwNsName: gwNsName,
302-
sectionName: sectionName,
305+
gwNsName: gwNsName,
306+
}
307+
308+
// If there is no section name, we create ParentRefs for each listener in the gateway
309+
if p.SectionName == nil {
310+
for _, l := range gw.Listeners {
311+
k.sectionName = string(l.Source.Name)
312+
313+
if err := checkUniqueSections(k); err != nil {
314+
return nil, err
315+
}
316+
317+
sectionNameRefs = append(sectionNameRefs, ParentRef{
318+
// if the ParentRefs we create are for each listener in the same gateway, we keep the
319+
// parentRefIndex the same so when we look at a route's parentRef's we can see
320+
// if the parentRef is a unique parentRef or one we created internally
321+
Idx: i,
322+
Gateway: CreateParentRefGateway(gw),
323+
SectionName: &l.Source.Name,
324+
Port: p.Port,
325+
})
326+
}
327+
328+
continue
303329
}
304330

305-
if _, exist := uniqueSectionsPerGateway[k]; exist {
306-
return nil, fmt.Errorf("duplicate section name %q for Gateway %s", sectionName, gwNsName.String())
331+
k.sectionName = string(*p.SectionName)
332+
if err := checkUniqueSections(k); err != nil {
333+
return nil, err
307334
}
308-
uniqueSectionsPerGateway[k] = struct{}{}
309335

310336
sectionNameRefs = append(sectionNameRefs, ParentRef{
311337
Idx: i,

internal/controller/state/graph/route_common_test.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func TestBuildSectionNameRefs(t *testing.T) {
2525

2626
gwNsName1 := types.NamespacedName{Namespace: routeNamespace, Name: "gateway-1"}
2727
gwNsName2 := types.NamespacedName{Namespace: routeNamespace, Name: "gateway-2"}
28+
gwNsName3 := types.NamespacedName{Namespace: routeNamespace, Name: "gateway-3"}
2829

2930
parentRefs := []gatewayv1.ParentReference{
3031
{
@@ -51,6 +52,10 @@ func TestBuildSectionNameRefs(t *testing.T) {
5152
Name: gatewayv1.ObjectName("some-other-gateway"),
5253
SectionName: helpers.GetPointer[gatewayv1.SectionName]("same-name"),
5354
},
55+
{
56+
Name: gatewayv1.ObjectName(gwNsName3.Name),
57+
SectionName: nil,
58+
},
5459
}
5560

5661
gws := map[types.NamespacedName]*Gateway{
@@ -70,6 +75,26 @@ func TestBuildSectionNameRefs(t *testing.T) {
7075
},
7176
},
7277
},
78+
gwNsName3: {
79+
Listeners: []*Listener{
80+
{
81+
Source: gatewayv1.Listener{
82+
Name: "http",
83+
},
84+
},
85+
{
86+
Source: gatewayv1.Listener{
87+
Name: "https",
88+
},
89+
},
90+
},
91+
Source: &gatewayv1.Gateway{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: gwNsName3.Name,
94+
Namespace: gwNsName3.Namespace,
95+
},
96+
},
97+
},
7398
}
7499

75100
expected := []ParentRef{
@@ -93,6 +118,16 @@ func TestBuildSectionNameRefs(t *testing.T) {
93118
Gateway: CreateParentRefGateway(gws[gwNsName2]),
94119
SectionName: parentRefs[4].SectionName,
95120
},
121+
{
122+
Idx: 6,
123+
Gateway: CreateParentRefGateway(gws[gwNsName3]),
124+
SectionName: helpers.GetPointer[gatewayv1.SectionName]("http"),
125+
},
126+
{
127+
Idx: 6,
128+
Gateway: CreateParentRefGateway(gws[gwNsName3]),
129+
SectionName: helpers.GetPointer[gatewayv1.SectionName]("https"),
130+
},
96131
}
97132

98133
tests := []struct {
@@ -124,16 +159,16 @@ func TestBuildSectionNameRefs(t *testing.T) {
124159
{
125160
parentRefs: []gatewayv1.ParentReference{
126161
{
127-
Name: gatewayv1.ObjectName(gwNsName1.Name),
128-
SectionName: nil,
162+
Name: gatewayv1.ObjectName(gwNsName3.Name),
163+
SectionName: helpers.GetPointer[gatewayv1.SectionName]("http"),
129164
},
130165
{
131-
Name: gatewayv1.ObjectName(gwNsName1.Name),
166+
Name: gatewayv1.ObjectName(gwNsName3.Name),
132167
SectionName: nil,
133168
},
134169
},
135-
name: "nil sectionNames",
136-
expectedError: errors.New("duplicate section name \"\" for Gateway test/gateway-1"),
170+
name: "duplicate sectionNames when one parentRef has no sectionName",
171+
expectedError: errors.New("duplicate section name \"http\" for Gateway test/gateway-3"),
137172
},
138173
}
139174

internal/controller/status/prepare_requests.go

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,41 @@ func PrepareRouteRequests(
9595
return reqs
9696
}
9797

98+
// removeDuplicateIndexParentRefs removes duplicate ParentRefs by Idx, keeping the first occurrence.
99+
// If an Idx is duplicated, the SectionName for the stored ParentRef is nil.
100+
func removeDuplicateIndexParentRefs(parentRefs []graph.ParentRef) []graph.ParentRef {
101+
idxToParentRef := make(map[int][]graph.ParentRef)
102+
for _, ref := range parentRefs {
103+
idxToParentRef[ref.Idx] = append(idxToParentRef[ref.Idx], ref)
104+
}
105+
106+
results := make([]graph.ParentRef, len(idxToParentRef))
107+
108+
for idx, refs := range idxToParentRef {
109+
if len(refs) == 1 {
110+
results[idx] = refs[0]
111+
continue
112+
}
113+
114+
winningParentRef := graph.ParentRef{
115+
Idx: idx,
116+
Gateway: refs[0].Gateway,
117+
Attachment: refs[0].Attachment,
118+
}
119+
120+
for _, ref := range refs {
121+
if ref.Attachment.Attached {
122+
if len(ref.Attachment.FailedConditions) == 0 || winningParentRef.Attachment == nil {
123+
winningParentRef.Attachment = ref.Attachment
124+
}
125+
}
126+
}
127+
results[idx] = winningParentRef
128+
}
129+
130+
return results
131+
}
132+
98133
func prepareRouteStatus(
99134
gatewayCtlrName string,
100135
parentRefs []graph.ParentRef,
@@ -103,11 +138,17 @@ func prepareRouteStatus(
103138
transitionTime metav1.Time,
104139
srcGeneration int64,
105140
) v1.RouteStatus {
106-
parents := make([]v1.RouteParentStatus, 0, len(parentRefs))
141+
// If a route did not specify a sectionName in its parentRefs section, it will attempt to attach to all available
142+
// listeners. In this case, parentRefs will be created and attached to the route for each attachable listener.
143+
// These parentRefs will all have the same Idx, and in order to not duplicate route statuses for the same Gateway,
144+
// we need to remove these duplicates. Additionally, we remove the sectionName.
145+
processedParentRefs := removeDuplicateIndexParentRefs(parentRefs)
146+
147+
parents := make([]v1.RouteParentStatus, 0, len(processedParentRefs))
107148

108149
defaultConds := conditions.NewDefaultRouteConditions()
109150

110-
for _, ref := range parentRefs {
151+
for _, ref := range processedParentRefs {
111152
failedAttachmentCondCount := 0
112153
if ref.Attachment != nil {
113154
failedAttachmentCondCount = len(ref.Attachment.FailedConditions)

0 commit comments

Comments
 (0)