| // 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...)) |
| }) |
| } |
| } |