Allow multiple listeners on same port in Gateway API provider
This commit is contained in:
parent
804b0ff2f2
commit
55ba4356f2
3 changed files with 99 additions and 15 deletions
|
@ -21,12 +21,13 @@ spec:
|
||||||
kind: Gateway
|
kind: Gateway
|
||||||
apiVersion: gateway.networking.k8s.io/v1alpha2
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||||
metadata:
|
metadata:
|
||||||
name: my-gateway
|
name: my-gateway-http
|
||||||
namespace: default
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
gatewayClassName: my-gateway-class
|
gatewayClassName: my-gateway-class
|
||||||
listeners: # Use GatewayClass defaults for listener definition.
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
- name: http1
|
- name: http1
|
||||||
|
hostname: foo.bar
|
||||||
protocol: HTTP
|
protocol: HTTP
|
||||||
port: 9080
|
port: 9080
|
||||||
allowedRoutes:
|
allowedRoutes:
|
||||||
|
@ -37,6 +38,7 @@ spec:
|
||||||
from: Same
|
from: Same
|
||||||
|
|
||||||
- name: http2
|
- name: http2
|
||||||
|
hostname: foo.bar
|
||||||
protocol: HTTP
|
protocol: HTTP
|
||||||
port: 9080
|
port: 9080
|
||||||
allowedRoutes:
|
allowedRoutes:
|
||||||
|
@ -45,7 +47,26 @@ spec:
|
||||||
namespaces:
|
namespaces:
|
||||||
from: Same
|
from: Same
|
||||||
|
|
||||||
- name: tcp
|
---
|
||||||
|
kind: Gateway
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||||
|
metadata:
|
||||||
|
name: my-gateway-tcp
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
gatewayClassName: my-gateway-class
|
||||||
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
|
- name: tcp1
|
||||||
|
hostname: foo.bar
|
||||||
|
protocol: TCP
|
||||||
|
port: 9000
|
||||||
|
allowedRoutes:
|
||||||
|
kinds:
|
||||||
|
- kind: TCPRoute
|
||||||
|
namespaces:
|
||||||
|
from: Same
|
||||||
|
- name: tcp2
|
||||||
|
hostname: foo.bar
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
port: 9000
|
port: 9000
|
||||||
allowedRoutes:
|
allowedRoutes:
|
||||||
|
@ -54,7 +75,17 @@ spec:
|
||||||
namespaces:
|
namespaces:
|
||||||
from: Same
|
from: Same
|
||||||
|
|
||||||
- name: tls
|
---
|
||||||
|
kind: Gateway
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
||||||
|
metadata:
|
||||||
|
name: my-gateway-tls
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
gatewayClassName: my-gateway-class
|
||||||
|
listeners: # Use GatewayClass defaults for listener definition.
|
||||||
|
- name: tls1
|
||||||
|
hostname: foo.bar
|
||||||
protocol: TLS
|
protocol: TLS
|
||||||
port: 9000
|
port: 9000
|
||||||
tls:
|
tls:
|
||||||
|
@ -66,6 +97,7 @@ spec:
|
||||||
from: Same
|
from: Same
|
||||||
|
|
||||||
- name: tls2
|
- name: tls2
|
||||||
|
hostname: foo.bar
|
||||||
protocol: TLS
|
protocol: TLS
|
||||||
port: 9000
|
port: 9000
|
||||||
tls:
|
tls:
|
|
@ -317,7 +317,7 @@ func (p *Provider) createGatewayConf(ctx context.Context, client Client, gateway
|
||||||
func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *v1alpha2.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []v1alpha2.ListenerStatus {
|
func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *v1alpha2.Gateway, conf *dynamic.Configuration, tlsConfigs map[string]*tls.CertAndStores) []v1alpha2.ListenerStatus {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
listenerStatuses := make([]v1alpha2.ListenerStatus, len(gateway.Spec.Listeners))
|
listenerStatuses := make([]v1alpha2.ListenerStatus, len(gateway.Spec.Listeners))
|
||||||
allocatedPort := map[v1alpha2.PortNumber]v1alpha2.ProtocolType{}
|
allocatedListeners := make(map[string]struct{})
|
||||||
|
|
||||||
for i, listener := range gateway.Spec.Listeners {
|
for i, listener := range gateway.Spec.Listeners {
|
||||||
listenerStatuses[i] = v1alpha2.ListenerStatus{
|
listenerStatuses[i] = v1alpha2.ListenerStatus{
|
||||||
|
@ -340,19 +340,22 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := allocatedPort[listener.Port]; ok {
|
listenerKey := makeListenerKey(listener)
|
||||||
|
|
||||||
|
if _, ok := allocatedListeners[listenerKey]; ok {
|
||||||
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
|
||||||
Type: string(v1alpha2.ListenerConditionDetached),
|
Type: string(v1alpha2.ListenerConditionConflicted),
|
||||||
Status: metav1.ConditionTrue,
|
Status: metav1.ConditionTrue,
|
||||||
LastTransitionTime: metav1.Now(),
|
LastTransitionTime: metav1.Now(),
|
||||||
Reason: string(v1alpha2.ListenerReasonPortUnavailable),
|
Reason: "DuplicateListener",
|
||||||
Message: fmt.Sprintf("Port %d unavailable", listener.Port),
|
Message: "A listener with same protocol, port and hostname already exists",
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
allocatedPort[listener.Port] = listener.Protocol
|
allocatedListeners[listenerKey] = struct{}{}
|
||||||
|
|
||||||
ep, err := p.entryPointName(listener.Port, listener.Protocol)
|
ep, err := p.entryPointName(listener.Port, listener.Protocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// update "Detached" status with "PortUnavailable" reason
|
// update "Detached" status with "PortUnavailable" reason
|
||||||
|
@ -1700,3 +1703,13 @@ func isInternalService(ref v1alpha2.BackendRef) bool {
|
||||||
*ref.Group == traefikv1alpha1.GroupName &&
|
*ref.Group == traefikv1alpha1.GroupName &&
|
||||||
strings.HasSuffix(string(ref.Name), "@internal")
|
strings.HasSuffix(string(ref.Name), "@internal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeListenerKey joins protocol, hostname, and port of a listener into a string key.
|
||||||
|
func makeListenerKey(l v1alpha2.Listener) string {
|
||||||
|
var hostname v1alpha2.Hostname
|
||||||
|
if l.Hostname != nil {
|
||||||
|
hostname = *l.Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port)
|
||||||
|
}
|
||||||
|
|
|
@ -3566,8 +3566,8 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Empty caused by mixed routes multiple protocol using same port",
|
desc: "Empty caused by mixed routes with multiple listeners using same hostname, port and protocol",
|
||||||
paths: []string{"services.yml", "mixed/with_multiple_protocol_using_same_port.yml"},
|
paths: []string{"services.yml", "mixed/with_multiple_listeners_using_same_hostname_port_protocol.yml"},
|
||||||
entryPoints: map[string]Entrypoint{
|
entryPoints: map[string]Entrypoint{
|
||||||
"web": {Address: ":9080"},
|
"web": {Address: ":9080"},
|
||||||
"tcp": {Address: ":9000"},
|
"tcp": {Address: ":9000"},
|
||||||
|
@ -4989,7 +4989,7 @@ func Test_shouldAttach(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_matchingHostnames(t *testing.T) {
|
func Test_matchingHostnames(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
listener v1alpha2.Listener
|
listener v1alpha2.Listener
|
||||||
hostnames []v1alpha2.Hostname
|
hostnames []v1alpha2.Hostname
|
||||||
|
@ -5081,7 +5081,7 @@ func Test_matchingHostnames(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -5093,7 +5093,7 @@ func Test_matchingHostnames(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_getAllowedRoutes(t *testing.T) {
|
func Test_getAllowedRoutes(t *testing.T) {
|
||||||
tests := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
listener v1alpha2.Listener
|
listener v1alpha2.Listener
|
||||||
supportedRouteKinds []v1alpha2.RouteGroupKind
|
supportedRouteKinds []v1alpha2.RouteGroupKind
|
||||||
|
@ -5193,7 +5193,7 @@ func Test_getAllowedRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -5210,6 +5210,45 @@ func Test_getAllowedRoutes(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_makeListenerKey(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
listener v1alpha2.Listener
|
||||||
|
expectedKey string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
expectedKey: "||0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "listener with port, protocol and hostname",
|
||||||
|
listener: v1alpha2.Listener{
|
||||||
|
Port: 443,
|
||||||
|
Protocol: v1alpha2.HTTPSProtocolType,
|
||||||
|
Hostname: hostnamePtr("www.example.com"),
|
||||||
|
},
|
||||||
|
expectedKey: "HTTPS|www.example.com|443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "listener with port, protocol and nil hostname",
|
||||||
|
listener: v1alpha2.Listener{
|
||||||
|
Port: 443,
|
||||||
|
Protocol: v1alpha2.HTTPSProtocolType,
|
||||||
|
},
|
||||||
|
expectedKey: "HTTPS||443",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedKey, makeListenerKey(test.listener))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func hostnamePtr(hostname v1alpha2.Hostname) *v1alpha2.Hostname {
|
func hostnamePtr(hostname v1alpha2.Hostname) *v1alpha2.Hostname {
|
||||||
return &hostname
|
return &hostname
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue