blob: 031b0fe5ab359b729e8f314e8c4e1efad2e60bba [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)
func TestListenersForGatewayContext(t *testing.T) {
hostname := gatewayv1.Hostname("example.com")
listener := gatewayv1.Listener{
Name: "http",
Protocol: gatewayv1.HTTPProtocolType,
Port: gatewayv1.PortNumber(80),
Hostname: &hostname,
}
tests := []struct {
name string
context RouteParentRefContext
expected []gatewayv1.Listener
}{
{
name: "prefer listeners slice when present",
context: RouteParentRefContext{
Listeners: []gatewayv1.Listener{
listener,
{
Name: "https",
Protocol: gatewayv1.HTTPSProtocolType,
Port: gatewayv1.PortNumber(443),
},
},
Listener: &gatewayv1.Listener{
Name: "ignored",
},
},
expected: []gatewayv1.Listener{
listener,
{
Name: "https",
Protocol: gatewayv1.HTTPSProtocolType,
Port: gatewayv1.PortNumber(443),
},
},
},
{
name: "fallback to single listener pointer",
context: RouteParentRefContext{
Listener: &listener,
},
expected: []gatewayv1.Listener{
listener,
},
},
{
name: "no matched listeners",
context: RouteParentRefContext{},
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, listenersForGatewayContext(tt.context))
})
}
}
func TestGetUnionOfGatewayHostnames(t *testing.T) {
fooHostname := gatewayv1.Hostname("foo.example.com")
barHostname := gatewayv1.Hostname("bar.example.com")
t.Run("uses all matched listeners and ignores non-hostname-effective listeners", func(t *testing.T) {
gateways := []RouteParentRefContext{
{
Listeners: []gatewayv1.Listener{
{Name: "foo", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &fooHostname},
{Name: "bar", Protocol: gatewayv1.HTTPSProtocolType, Port: gatewayv1.PortNumber(443), Hostname: &barHostname},
{Name: "tcp", Protocol: gatewayv1.TCPProtocolType, Port: gatewayv1.PortNumber(9100)},
},
},
}
hostnames, matchAny := getUnionOfGatewayHostnames(gateways)
assert.False(t, matchAny)
assert.Equal(t, []gatewayv1.Hostname{fooHostname, barHostname}, hostnames)
})
t.Run("listener without hostname matches any hostname", func(t *testing.T) {
gateways := []RouteParentRefContext{
{
Listeners: []gatewayv1.Listener{
{Name: "http", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80)},
},
},
}
hostnames, matchAny := getUnionOfGatewayHostnames(gateways)
assert.True(t, matchAny)
assert.Nil(t, hostnames)
})
}
func TestGetMinimumHostnameIntersection(t *testing.T) {
fooHostname := gatewayv1.Hostname("foo.example.com")
routeHostname := gatewayv1.Hostname("foo.example.com")
wildcardHostname := gatewayv1.Hostname("*.example.com")
t.Run("matches across multiple listeners without sectionName", func(t *testing.T) {
gateways := []RouteParentRefContext{
{
Listeners: []gatewayv1.Listener{
{Name: "foo", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &fooHostname},
{Name: "wildcard", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &wildcardHostname},
},
},
}
assert.Equal(t, routeHostname, getMinimumHostnameIntersection(gateways, routeHostname))
})
t.Run("returns empty when there is no listener match", func(t *testing.T) {
gateways := []RouteParentRefContext{
{
Listeners: []gatewayv1.Listener{
{Name: "foo", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &fooHostname},
},
},
}
unmatched := gatewayv1.Hostname("bar.example.com")
assert.Equal(t, gatewayv1.Hostname(""), getMinimumHostnameIntersection(gateways, unmatched))
})
}
func TestFilterHostnamesWithMatchedListeners(t *testing.T) {
fooHostname := gatewayv1.Hostname("foo.example.com")
barHostname := gatewayv1.Hostname("bar.example.com")
route := &gatewayv1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: gatewayv1.HTTPRouteSpec{
Hostnames: []gatewayv1.Hostname{
fooHostname,
barHostname,
},
},
}
gateways := []RouteParentRefContext{
{
Listeners: []gatewayv1.Listener{
{Name: "foo", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &fooHostname},
{Name: "bar", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &barHostname},
},
},
}
filtered, err := filterHostnames(gateways, route.DeepCopy())
assert.NoError(t, err)
assert.Equal(t, []gatewayv1.Hostname{fooHostname, barHostname}, filtered.Spec.Hostnames)
}
func TestFilterHostnamesNoMatchedListeners(t *testing.T) {
fooHostname := gatewayv1.Hostname("foo.example.com")
barHostname := gatewayv1.Hostname("bar.example.com")
route := &gatewayv1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: gatewayv1.HTTPRouteSpec{
Hostnames: []gatewayv1.Hostname{
barHostname,
},
},
}
gateways := []RouteParentRefContext{
{
Listeners: []gatewayv1.Listener{
{Name: "foo", Protocol: gatewayv1.HTTPProtocolType, Port: gatewayv1.PortNumber(80), Hostname: &fooHostname},
},
},
}
_, err := filterHostnames(gateways, route.DeepCopy())
assert.ErrorIs(t, err, ErrNoMatchingListenerHostname)
}
func TestAppendListeners(t *testing.T) {
listenerA := gatewayv1.Listener{Name: "a", Port: 80}
listenerB := gatewayv1.Listener{Name: "b", Port: 81}
listenerA2 := gatewayv1.Listener{Name: "a", Port: 82}
listenerA3 := gatewayv1.Listener{Name: "a", Port: 80}
tests := []struct {
name string
target []gatewayv1.Listener
source []gatewayv1.Listener
expected []gatewayv1.Listener
}{
{
name: "empty target, add listeners",
target: nil,
source: []gatewayv1.Listener{listenerA, listenerB},
expected: []gatewayv1.Listener{listenerA, listenerB},
},
{
name: "preserves same listener names from different gateways",
target: []gatewayv1.Listener{listenerA},
source: []gatewayv1.Listener{listenerA, listenerB},
expected: []gatewayv1.Listener{listenerA, listenerA, listenerB},
},
{
name: "preserves all listeners when names collide",
target: []gatewayv1.Listener{listenerA},
source: []gatewayv1.Listener{listenerB, listenerA2, listenerA3},
expected: []gatewayv1.Listener{listenerA, listenerB, listenerA2, listenerA3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, appendListeners(tt.target, tt.source...))
})
}
}