Skip to content

Commit ea14a08

Browse files
committed
fix ListenerSet conformance test
Signed-off-by: zirain <zirain2009@gmail.com>
1 parent 53b9963 commit ea14a08

22 files changed

Lines changed: 2632 additions & 181 deletions

internal/gatewayapi/contexts.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ func (g *GatewayContext) attachEnvoyProxy(resources *resource.Resources, epMap m
121121
return err
122122
}
123123

124-
func (g *GatewayContext) IncreaseAttachedListenerSets() {
125-
if g.Status.AttachedListenerSets == nil {
126-
g.Status.AttachedListenerSets = new(int32(1))
127-
} else {
128-
*g.Status.AttachedListenerSets++
124+
func (g *GatewayContext) SetAttachedListenerSets(count int32) {
125+
if count <= 0 {
126+
g.Status.AttachedListenerSets = nil
127+
return
129128
}
129+
g.Status.AttachedListenerSets = &count
130130
}
131131

132132
// ListenerContext wraps a Listener and provides helper methods for

internal/gatewayapi/helpers.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -425,24 +425,24 @@ func wildcardHostnameMatchesHostname(wildcardHostname, hostname string) bool {
425425

426426
func containsPort(ports []*protocolPort, port *protocolPort) bool {
427427
for _, protocolPort := range ports {
428-
curProtocol, curLevel := layer4Protocol(protocolPort)
429-
myProtocol, myLevel := layer4Protocol(port)
430-
if protocolPort.port == port.port && (curProtocol == myProtocol && curLevel == myLevel) {
428+
curProtocol := layer4Protocol(protocolPort)
429+
myProtocol := layer4Protocol(port)
430+
if protocolPort.port == port.port && curProtocol == myProtocol {
431431
return true
432432
}
433433
}
434434
return false
435435
}
436436

437-
// layer4Protocol returns listener L4 protocol and listen protocol level
438-
func layer4Protocol(protocolPort *protocolPort) (string, string) {
437+
// layer4Protocol returns listener L4 protocol
438+
func layer4Protocol(protocolPort *protocolPort) string {
439439
switch protocolPort.protocol {
440440
case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType, gwapiv1.TLSProtocolType:
441-
return TCPProtocol, L7Protocol
441+
return TCPProtocol
442442
case gwapiv1.TCPProtocolType:
443-
return TCPProtocol, L4Protocol
443+
return TCPProtocol
444444
default:
445-
return UDPProtocol, L4Protocol
445+
return UDPProtocol
446446
}
447447
}
448448

internal/gatewayapi/listener.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,33 @@ func allowedRouteKindsForProtocol(protocol gwapiv1.ProtocolType, tlsMode *gwapiv
239239
}
240240
}
241241

242+
// validateProtocolRules validates protocol-specific constraints (hostname, TLS requirements).
243+
// Returns true if all constraints are satisfied, false otherwise.
244+
func validateProtocolRules(listener *ListenerContext) bool {
245+
switch listener.Protocol {
246+
case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType, gwapiv1.TLSProtocolType,
247+
gwapiv1.TCPProtocolType, gwapiv1.UDPProtocolType:
248+
// All supported protocols pass basic validation here.
249+
// Protocol-specific constraints (TLS, hostname) are validated in
250+
// validateTLSConfiguration and validateHostName respectively,
251+
// where they can set proper conditions.
252+
return true
253+
254+
default:
255+
// Unsupported protocol handled separately
256+
return false
257+
}
258+
}
259+
242260
func (t *Translator) validateListenerSpec(listener *ListenerContext, resources *resource.Resources) bool {
243261
// Validate listener spec directly without relying on conditions.
244262
// Start with valid assumption and invalidate on failures.
263+
245264
// Phase 1: Validate fundamental rules
246265
specValid := t.validateAllowedNamespaces(listener)
266+
if !validateProtocolRules(listener) {
267+
specValid = false
268+
}
247269

248270
// Phase 2: Validate allowed routes based on protocol
249271
if isSupportedListenerProtocol(listener.Protocol) {
@@ -313,7 +335,6 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
313335
}
314336
t.processProxyReadyListener(xdsIR[irKey], gateway.envoyProxy)
315337
t.processProxyObservability(gateway, xdsIR[irKey], infraIR[irKey].Proxy, resources)
316-
317338
for _, listener := range gateway.listeners {
318339
// Finalize listener conditions and check readiness.
319340
t.validateListenerConditions(listener)
@@ -411,6 +432,29 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
411432
t.checkOverlappingTLSConfig(gateways)
412433
}
413434

435+
// isListenerReady returns true if the listener is ready (Accepted=True and Programmed=True or no conditions yet).
436+
// A listener is not ready if it has Accepted=False or Programmed=False.
437+
func isListenerReady(listener *ListenerContext) bool {
438+
conditions := listener.GetConditions()
439+
440+
// No conditions yet means it will be set to ready during validation.
441+
if len(conditions) == 0 {
442+
return true
443+
}
444+
445+
// Check if Accepted=False or Programmed=False exists.
446+
for _, cond := range conditions {
447+
if cond.Type == string(gwapiv1.ListenerConditionAccepted) && cond.Status == metav1.ConditionFalse {
448+
return false
449+
}
450+
if cond.Type == string(gwapiv1.ListenerConditionProgrammed) && cond.Status == metav1.ConditionFalse {
451+
return false
452+
}
453+
}
454+
455+
return true
456+
}
457+
414458
// checkOverlappingTLSConfig checks for overlapping hostnames and certificates between listeners and sets
415459
// the `OverlappingTLSConfig` condition if there are overlapping hostnames or certificates.
416460
func (t *Translator) checkOverlappingTLSConfig(gateways []*GatewayContext) {
@@ -419,7 +463,7 @@ func (t *Translator) checkOverlappingTLSConfig(gateways []*GatewayContext) {
419463
httpsListeners := []*ListenerContext{}
420464
for _, gateway := range gateways {
421465
for _, listener := range gateway.listeners {
422-
if listener.Protocol == gwapiv1.HTTPSProtocolType {
466+
if listener.Protocol == gwapiv1.HTTPSProtocolType && isListenerReady(listener) {
423467
httpsListeners = append(httpsListeners, listener)
424468
}
425469
}
@@ -434,7 +478,7 @@ func (t *Translator) checkOverlappingTLSConfig(gateways []*GatewayContext) {
434478
for _, gateway := range gateways {
435479
httpsListeners := []*ListenerContext{}
436480
for _, listener := range gateway.listeners {
437-
if listener.Protocol == gwapiv1.HTTPSProtocolType {
481+
if listener.Protocol == gwapiv1.HTTPSProtocolType && isListenerReady(listener) {
438482
httpsListeners = append(httpsListeners, listener)
439483
}
440484
}

internal/gatewayapi/listenerset.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ func (t *Translator) processListenerSet(ls *gwapiv1.ListenerSet, gatewayMap map[
9393
}
9494
gatewayCtx.listeners = append(gatewayCtx.listeners, listenerCtx)
9595
}
96-
gatewayCtx.IncreaseAttachedListenerSets()
9796
}
9897

9998
// ProcessListenerSetStatus computes the status of ListenerSets after their listeners have been processed.
@@ -158,6 +157,43 @@ func (t *Translator) ProcessListenerSetStatus(listenerSets []*gwapiv1.ListenerSe
158157
}
159158
}
160159

160+
// UpdateGatewayAttachedListenerSetCount sets Gateway.status.attachedListenerSets based on
161+
// ListenerSet Accepted=True status.
162+
func (t *Translator) UpdateGatewayAttachedListenerSetCount(listenerSets []*gwapiv1.ListenerSet, gateways []*GatewayContext) {
163+
gatewayMap := make(map[types.NamespacedName]*GatewayContext, len(gateways))
164+
for _, gw := range gateways {
165+
gatewayMap[types.NamespacedName{Namespace: gw.Namespace, Name: gw.Name}] = gw
166+
// reset for current reconciliation cycle
167+
gw.SetAttachedListenerSets(0)
168+
}
169+
170+
counts := make(map[types.NamespacedName]int32)
171+
for _, ls := range listenerSets {
172+
if !listenerSetAccepted(ls) {
173+
continue
174+
}
175+
parentNamespace := NamespaceDerefOr(ls.Spec.ParentRef.Namespace, ls.Namespace)
176+
key := types.NamespacedName{Namespace: parentNamespace, Name: string(ls.Spec.ParentRef.Name)}
177+
if _, ok := gatewayMap[key]; !ok {
178+
continue
179+
}
180+
counts[key]++
181+
}
182+
183+
for key, count := range counts {
184+
gatewayMap[key].SetAttachedListenerSets(count)
185+
}
186+
}
187+
188+
func listenerSetAccepted(ls *gwapiv1.ListenerSet) bool {
189+
for _, cond := range ls.Status.Conditions {
190+
if cond.Type == string(gwapiv1.ListenerSetConditionAccepted) {
191+
return cond.Status == metav1.ConditionTrue
192+
}
193+
}
194+
return false
195+
}
196+
161197
func (t *Translator) isListenerSetAllowed(gateway *gwapiv1.Gateway, ls *gwapiv1.ListenerSet) bool {
162198
// If AllowedListeners is not set, attachment is not allowed (default is None)
163199
if gateway.Spec.AllowedListeners == nil || gateway.Spec.AllowedListeners.Namespaces == nil || gateway.Spec.AllowedListeners.Namespaces.From == nil {

internal/gatewayapi/testdata/gateway-with-two-listeners-with-http-and-tlsroute-same-hostname-and-port.out.yaml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ gateways:
3333
status: "True"
3434
type: Conflicted
3535
- lastTransitionTime: null
36-
message: Listener is invalid, see other Conditions for details.
37-
reason: Invalid
36+
message: All listeners for a given port must use a unique hostname
37+
reason: HostnameConflict
38+
status: "False"
39+
type: Accepted
40+
- lastTransitionTime: null
41+
message: All listeners for a given port must use a unique hostname
42+
reason: HostnameConflict
3843
status: "False"
3944
type: Programmed
4045
- lastTransitionTime: null
@@ -56,8 +61,13 @@ gateways:
5661
status: "True"
5762
type: Conflicted
5863
- lastTransitionTime: null
59-
message: Listener is invalid, see other Conditions for details.
60-
reason: Invalid
64+
message: All listeners for a given port must use a unique hostname
65+
reason: HostnameConflict
66+
status: "False"
67+
type: Accepted
68+
- lastTransitionTime: null
69+
message: All listeners for a given port must use a unique hostname
70+
reason: HostnameConflict
6171
status: "False"
6272
type: Programmed
6373
- lastTransitionTime: null

internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-and-hostname.out.yaml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@ gateways:
3131
status: "True"
3232
type: Conflicted
3333
- lastTransitionTime: null
34-
message: Listener is invalid, see other Conditions for details.
35-
reason: Invalid
34+
message: All listeners for a given port must use a unique hostname
35+
reason: HostnameConflict
36+
status: "False"
37+
type: Accepted
38+
- lastTransitionTime: null
39+
message: All listeners for a given port must use a unique hostname
40+
reason: HostnameConflict
3641
status: "False"
3742
type: Programmed
3843
- lastTransitionTime: null
@@ -54,8 +59,13 @@ gateways:
5459
status: "True"
5560
type: Conflicted
5661
- lastTransitionTime: null
57-
message: Listener is invalid, see other Conditions for details.
58-
reason: Invalid
62+
message: All listeners for a given port must use a unique hostname
63+
reason: HostnameConflict
64+
status: "False"
65+
type: Accepted
66+
- lastTransitionTime: null
67+
message: All listeners for a given port must use a unique hostname
68+
reason: HostnameConflict
5969
status: "False"
6070
type: Programmed
6171
- lastTransitionTime: null

internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-and-incompatible-protocol.in.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ gateways:
2525
mode: Terminate
2626
certificateRefs:
2727
- name: tls-secret-1
28+
- allowedRoutes:
29+
namespaces:
30+
from: All
31+
name: udp-8162
32+
port: 8162
33+
protocol: UDP
34+
# This listener(http-8162) is valid because HTTP + UDP are compatible protocols.
35+
- allowedRoutes:
36+
namespaces:
37+
from: All
38+
name: http-8162
39+
port: 8162
40+
protocol: HTTP
41+
# This listener(tcp-8162) is invalid because TCP is not compatible with HTTP.
42+
- allowedRoutes:
43+
namespaces:
44+
from: All
45+
name: tcp-8162
46+
port: 8162
47+
protocol: TCP
2848
secrets:
2949
- apiVersion: v1
3050
kind: Secret

0 commit comments

Comments
 (0)