Skip to content

Commit 8e60f67

Browse files
author
Kate Osborn
committed
Add port conflict resolver; remove hostname conflict resolver
1 parent 6b39728 commit 8e60f67

File tree

5 files changed

+75
-78
lines changed

5 files changed

+75
-78
lines changed

docs/gateway-api-compatibility.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ Fields:
8686
* `Accepted/True/Accepted`
8787
* `Accepted/False/UnsupportedProtocol`
8888
* `Accepted/False/InvalidCertificateRef`
89-
* `Accepted/False/HostnameConflict`
89+
* `Accepted/False/ProtocolConflict`
9090
* `Accepted/False/UnsupportedValue`: Custom reason for when a value of a field in a Listener is invalid or not supported.
9191
* `Accepted/False/GatewayConflict`: Custom reason for when the Gateway is ignored due to a conflicting Gateway. NKG only supports a single Gateway.
9292
* `ResolvedRefs/True/ResolvedRefs`
9393
* `ResolvedRefs/False/InvalidCertificateRef`
94-
* `Conflicted/True/HostnameConflict`
94+
* `Conflicted/True/ProtocolConflict`
9595
* `Conflicted/False/NoConflicts`
9696

9797
### HTTPRoute

internal/state/change_processor_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,16 @@ var _ = Describe("ChangeProcessor", func() {
15901590
}
15911591
}),
15921592
),
1593+
Entry("listener hostnames conflict",
1594+
createInvalidGateway(func(gw *v1beta1.Gateway) {
1595+
gw.Spec.Listeners = append(gw.Spec.Listeners, v1beta1.Listener{
1596+
Name: "listener-80-2",
1597+
Hostname: nil,
1598+
Port: 80,
1599+
Protocol: v1beta1.HTTPProtocolType,
1600+
})
1601+
}),
1602+
),
15931603
)
15941604
})
15951605
})

internal/state/conditions/conditions.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,19 +317,20 @@ func NewListenerInvalidCertificateRef(msg string) []Condition {
317317
}
318318
}
319319

320-
// NewListenerConflictedHostname returns Conditions that indicate that a hostname of a Listener is conflicted.
321-
func NewListenerConflictedHostname(msg string) []Condition {
320+
// NewListenerProtocolConflict returns Conditions that indicate multiple Listeners are specified with the same
321+
// Listener port number, but have conflicting protocol specifications.
322+
func NewListenerProtocolConflict(msg string) []Condition {
322323
return []Condition{
323324
{
324325
Type: string(v1beta1.ListenerConditionAccepted),
325326
Status: metav1.ConditionFalse,
326-
Reason: string(v1beta1.ListenerReasonHostnameConflict),
327+
Reason: string(v1beta1.ListenerReasonProtocolConflict),
327328
Message: msg,
328329
},
329330
{
330331
Type: string(v1beta1.ListenerConditionConflicted),
331332
Status: metav1.ConditionTrue,
332-
Reason: string(v1beta1.ListenerReasonHostnameConflict),
333+
Reason: string(v1beta1.ListenerReasonProtocolConflict),
333334
Message: msg,
334335
},
335336
}

internal/state/graph/gateway_listener.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func newListenerConfiguratorFactory(
6868
gw *v1beta1.Gateway,
6969
secretMemoryMgr secrets.SecretDiskMemoryManager,
7070
) *listenerConfiguratorFactory {
71-
sharedHostnameConflictResolver := createHostnameConflictResolver()
71+
sharedPortConflictResolver := createPortConflictResolver()
7272

7373
return &listenerConfiguratorFactory{
7474
unsupportedProtocol: &listenerConfigurator{
@@ -91,7 +91,7 @@ func newListenerConfiguratorFactory(
9191
validateHTTPListener,
9292
},
9393
conflictResolvers: []listenerConflictResolver{
94-
sharedHostnameConflictResolver,
94+
sharedPortConflictResolver,
9595
},
9696
},
9797
https: &listenerConfigurator{
@@ -102,7 +102,7 @@ func newListenerConfiguratorFactory(
102102
createHTTPSListenerValidator(gw.Namespace),
103103
},
104104
conflictResolvers: []listenerConflictResolver{
105-
sharedHostnameConflictResolver,
105+
sharedPortConflictResolver,
106106
},
107107
externalReferenceResolvers: []listenerExternalReferenceResolver{
108108
createExternalReferencesForTLSSecretsResolver(gw.Namespace, secretMemoryMgr),
@@ -335,31 +335,47 @@ func createHTTPSListenerValidator(gwNsName string) listenerValidator {
335335
}
336336
}
337337

338-
func createHostnameConflictResolver() listenerConflictResolver {
339-
usedHostnamesByPort := make(map[v1beta1.PortNumber]map[string]*Listener)
338+
func createPortConflictResolver() listenerConflictResolver {
339+
conflictedPorts := make(map[v1beta1.PortNumber]bool)
340+
portProtocolOwner := make(map[v1beta1.PortNumber]v1beta1.ProtocolType)
341+
listenersByPort := make(map[v1beta1.PortNumber][]*Listener)
342+
343+
format := "Multiple listeners for the same port %d specify different protocols; " +
344+
"ensure only one protocol per port"
340345

341346
return func(l *Listener) {
342347
port := l.Source.Port
343-
h := getHostname(l.Source.Hostname)
344348

345-
if listenersForPort, portExists := usedHostnamesByPort[port]; portExists {
346-
if holder, holderExists := listenersForPort[h]; holderExists {
347-
l.Valid = false
348-
holder.Valid = false // all listeners for the same hostname/port become conflicted
349+
// if port is in map of conflictedPorts then we only need to set the current listener to invalid
350+
if conflictedPorts[port] {
351+
l.Valid = false
349352

350-
format := "Multiple listeners for the same port use the same hostname %q; " +
351-
"ensure only one listener uses that hostname"
352-
conflictedConds := conditions.NewListenerConflictedHostname(fmt.Sprintf(format, h))
353-
holder.Conditions = append(holder.Conditions, conflictedConds...)
354-
l.Conditions = append(l.Conditions, conflictedConds...)
355-
}
353+
conflictedConds := conditions.NewListenerProtocolConflict(fmt.Sprintf(format, port))
354+
l.Conditions = append(l.Conditions, conflictedConds...)
355+
return
356+
}
357+
358+
// otherwise, we add the listener to the list of listeners for this port
359+
// and then check if the protocol owner for the port is different from the current listener's protocol.
356360

357-
usedHostnamesByPort[port][h] = l
361+
listenersByPort[port] = append(listenersByPort[port], l)
358362

363+
protocol, ok := portProtocolOwner[port]
364+
if !ok {
365+
portProtocolOwner[port] = l.Source.Protocol
359366
return
360367
}
361368

362-
usedHostnamesByPort[port] = map[string]*Listener{h: l}
369+
// if protocol owner doesn't match the listener's protocol we mark the port as conflicted,
370+
// and invalidate all listeners we've seen for this port.
371+
if protocol != l.Source.Protocol {
372+
conflictedPorts[port] = true
373+
for _, l := range listenersByPort[port] {
374+
l.Valid = false
375+
conflictedConds := conditions.NewListenerProtocolConflict(fmt.Sprintf(format, port))
376+
l.Conditions = append(l.Conditions, conflictedConds...)
377+
}
378+
}
363379
}
364380
}
365381

internal/state/graph/gateway_test.go

Lines changed: 24 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,13 @@ func TestBuildGateway(t *testing.T) {
218218

219219
// foo http listeners
220220
foo80Listener1 := createHTTPListener("foo-80-1", "foo.example.com", 80)
221-
foo80Listener2 := createHTTPListener("foo-80-2", "foo.example.com", 80)
222221
foo8080Listener := createHTTPListener("foo-8080", "foo.example.com", 8080)
223222
foo8081Listener := createHTTPListener("foo-8081", "foo.example.com", 8081)
224223
foo443Listener := createHTTPListener("foo-443", "foo.example.com", 443)
225224

226225
// foo https listeners
227226
foo80HTTPSListener := createHTTPSListener("foo-80-https", "foo.example.com", 80, gatewayTLSConfig)
228227
foo443HTTPSListener1 := createHTTPSListener("foo-443-https-1", "foo.example.com", 443, gatewayTLSConfig)
229-
foo443HTTPSListener2 := createHTTPSListener("foo-443-https-2", "foo.example.com", 443, gatewayTLSConfig)
230228
foo8443HTTPSListener := createHTTPSListener("foo-8443-https", "foo.example.com", 8443, gatewayTLSConfig)
231229

232230
// bar http listener
@@ -255,8 +253,11 @@ func TestBuildGateway(t *testing.T) {
255253
"with an alphanumeric character (e.g. 'example.com', regex used for validation is " +
256254
`'[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`
257255

258-
conflictedHostnamesMsg = `Multiple listeners for the same port use the same hostname "foo.example.com"; ` +
259-
"ensure only one listener uses that hostname"
256+
conflict80PortMsg = "Multiple listeners for the same port 80 specify different protocols; " +
257+
"ensure only one protocol per port"
258+
259+
conflict443PortMsg = "Multiple listeners for the same port 443 specify different protocols; " +
260+
"ensure only one protocol per port"
260261

261262
secretPath = "/etc/nginx/secrets/test_secret"
262263
)
@@ -550,9 +551,11 @@ func TestBuildGateway(t *testing.T) {
550551
gatewayCfg{
551552
listeners: []v1beta1.Listener{
552553
foo80Listener1,
553-
foo80Listener2,
554+
bar80Listener,
555+
foo443Listener,
556+
foo80HTTPSListener,
554557
foo443HTTPSListener1,
555-
foo443HTTPSListener2,
558+
bar443HTTPSListener,
556559
},
557560
},
558561
),
@@ -564,78 +567,45 @@ func TestBuildGateway(t *testing.T) {
564567
Source: foo80Listener1,
565568
Valid: false,
566569
Routes: map[types.NamespacedName]*Route{},
567-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
568-
},
569-
"foo-80-2": {
570-
Source: foo80Listener2,
571-
Valid: false,
572-
Routes: map[types.NamespacedName]*Route{},
573-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
574-
},
575-
"foo-443-https-1": {
576-
Source: foo443HTTPSListener1,
577-
Valid: false,
578-
Routes: map[types.NamespacedName]*Route{},
579-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
580-
SecretPath: "/etc/nginx/secrets/test_secret",
581-
},
582-
"foo-443-https-2": {
583-
Source: foo443HTTPSListener2,
584-
Valid: false,
585-
Routes: map[types.NamespacedName]*Route{},
586-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
587-
SecretPath: "/etc/nginx/secrets/test_secret",
588-
},
589-
},
590-
Valid: true,
591-
},
592-
name: "collisions; same hostname, port, and protocol",
593-
},
594-
{
595-
gateway: createGateway(
596-
gatewayCfg{
597-
listeners: []v1beta1.Listener{
598-
foo80Listener1,
599-
foo443Listener,
600-
foo80HTTPSListener,
601-
foo443HTTPSListener1,
570+
Conditions: conditions.NewListenerProtocolConflict(conflict80PortMsg),
602571
},
603-
},
604-
),
605-
gatewayClass: validGC,
606-
expected: &Gateway{
607-
Source: getLastCreatedGetaway(),
608-
Listeners: map[string]*Listener{
609-
"foo-80-1": {
610-
Source: foo80Listener1,
572+
"bar-80": {
573+
Source: bar80Listener,
611574
Valid: false,
612575
Routes: map[types.NamespacedName]*Route{},
613-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
576+
Conditions: conditions.NewListenerProtocolConflict(conflict80PortMsg),
614577
},
615578
"foo-443": {
616579
Source: foo443Listener,
617580
Valid: false,
618581
Routes: map[types.NamespacedName]*Route{},
619-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
582+
Conditions: conditions.NewListenerProtocolConflict(conflict443PortMsg),
620583
},
621584
"foo-80-https": {
622585
Source: foo80HTTPSListener,
623586
Valid: false,
624587
Routes: map[types.NamespacedName]*Route{},
625-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
588+
Conditions: conditions.NewListenerProtocolConflict(conflict80PortMsg),
626589
SecretPath: "/etc/nginx/secrets/test_secret",
627590
},
628591
"foo-443-https-1": {
629592
Source: foo443HTTPSListener1,
630593
Valid: false,
631594
Routes: map[types.NamespacedName]*Route{},
632-
Conditions: conditions.NewListenerConflictedHostname(conflictedHostnamesMsg),
595+
Conditions: conditions.NewListenerProtocolConflict(conflict443PortMsg),
596+
SecretPath: "/etc/nginx/secrets/test_secret",
597+
},
598+
"bar-443-https": {
599+
Source: bar443HTTPSListener,
600+
Valid: false,
601+
Routes: map[types.NamespacedName]*Route{},
602+
Conditions: conditions.NewListenerProtocolConflict(conflict443PortMsg),
633603
SecretPath: "/etc/nginx/secrets/test_secret",
634604
},
635605
},
636606
Valid: true,
637607
},
638-
name: "collisions; same hostname and port but different protocols",
608+
name: "port/protocol collisions",
639609
},
640610
{
641611
gateway: createGateway(

0 commit comments

Comments
 (0)