Description:
With mergeGateways enabled, validateConflictedMergedListeners reserves a (protocol, hostname, port) key for every listener that passes isSpecValidForConflictChecks. That helper only reports per-listener spec validity, so a listener already rejected by an earlier conflict pass still reserves its key. A valid listener on another Gateway then collides with a reservation held by a listener that never reaches the IR, and the hostname ends up served by nobody.
Repro steps:
Enable mergeGateways. Gateway A puts HTTPS and TLS on the same port and hostname, which is admitted because the CRD's CEL rule keys on protocol as well as port and hostname. Gateway B has the only listener that can actually serve that hostname.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-a
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: foo.example.com
tls:
mode: Terminate
certificateRefs: [{kind: Secret, name: tls-secret-1}]
- name: tls
port: 443
protocol: TLS
hostname: foo.example.com
tls:
mode: Passthrough
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway-b
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: tls
port: 443
protocol: TLS
hostname: foo.example.com
tls:
mode: Passthrough
Gateway A's two listeners are correctly marked conflicted with no winner, per validateConflictedHostnameListeners, so neither is emitted. But both have already reserved their merged keys, so Gateway B's listener is marked conflicted too:
gateway-a https Conflicted=True/HostnameConflict
gateway-a tls Conflicted=True/HostnameConflict
gateway-b tls Conflicted=True/HostnameConflict <- should be Accepted
The resulting IR has no HTTP and no TCP listeners at all.
A second shape reaches the same place through the protocol pass. Gateway A has HTTP and HTTPS on port 443, so both are marked ProtocolConflict and excluded, yet the HTTPS listener still reserves foo.example.com:443, and Gateway B's TLS listener for that hostname is rejected.
Notes:
The no-winner branch in validateConflictedHostnameListeners calls setConflictedConditions but does not set hostnameConflictLoser, which is only set on the has-winner path. So there is currently no flag on ListenerContext that says "an earlier conflict pass excluded this listener" in the general case: protocolConflicted covers only protocol conflicts, and hostnameConflictLoser only hostname losers where a winner existed. Fixing this likely means adding a general conflicted flag, or checking the Conflicted condition, and auditing the existing readers of those two flags.
This predates #10013, which changes the merged key to use the protocol class. I checked both repro shapes with and without that change and the outcome is the same either way, since Gateway A's TLS listener reserves the TLS key regardless.
Description:
With
mergeGatewaysenabled,validateConflictedMergedListenersreserves a(protocol, hostname, port)key for every listener that passesisSpecValidForConflictChecks. That helper only reports per-listener spec validity, so a listener already rejected by an earlier conflict pass still reserves its key. A valid listener on another Gateway then collides with a reservation held by a listener that never reaches the IR, and the hostname ends up served by nobody.Repro steps:
Enable
mergeGateways. Gateway A puts HTTPS and TLS on the same port and hostname, which is admitted because the CRD's CEL rule keys on protocol as well as port and hostname. Gateway B has the only listener that can actually serve that hostname.Gateway A's two listeners are correctly marked conflicted with no winner, per
validateConflictedHostnameListeners, so neither is emitted. But both have already reserved their merged keys, so Gateway B's listener is marked conflicted too:The resulting IR has no HTTP and no TCP listeners at all.
A second shape reaches the same place through the protocol pass. Gateway A has HTTP and HTTPS on port 443, so both are marked
ProtocolConflictand excluded, yet the HTTPS listener still reservesfoo.example.com:443, and Gateway B's TLS listener for that hostname is rejected.Notes:
The no-winner branch in
validateConflictedHostnameListenerscallssetConflictedConditionsbut does not sethostnameConflictLoser, which is only set on the has-winner path. So there is currently no flag onListenerContextthat says "an earlier conflict pass excluded this listener" in the general case:protocolConflictedcovers only protocol conflicts, andhostnameConflictLoseronly hostname losers where a winner existed. Fixing this likely means adding a general conflicted flag, or checking the Conflicted condition, and auditing the existing readers of those two flags.This predates #10013, which changes the merged key to use the protocol class. I checked both repro shapes with and without that change and the outcome is the same either way, since Gateway A's TLS listener reserves the TLS key regardless.