Set Gateway HTTPRoute status
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
9d8fd24730
commit
05d2c86074
6 changed files with 995 additions and 883 deletions
|
@ -3,6 +3,8 @@ package gateway
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -12,16 +14,35 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/provider"
|
||||
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/utils/ptr"
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
|
||||
gatefake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
func init() {
|
||||
// required by k8s.MustParseYaml
|
||||
if err := gatev1.AddToScheme(kscheme.Scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gatev1beta1.AddToScheme(kscheme.Scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gatev1alpha2.AddToScheme(kscheme.Scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadHTTPRoutes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -1248,10 +1269,10 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-http-app-1-my-gateway-web-4a1b73e6f83804949a37": {
|
||||
"default-http-app-1-my-gateway-web-6cf37fa71907768d925c": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-http-app-1-my-gateway-web-4a1b73e6f83804949a37-wrr",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`) && Header(`my-header`,`foo`) && Header(`my-header2`,`bar`)",
|
||||
Service: "default-http-app-1-my-gateway-web-6cf37fa71907768d925c-wrr",
|
||||
Rule: "Host(`foo.com`) && (Path(`/bar`) || PathPrefix(`/bar/`)) && Header(`my-header`,`foo`) && Header(`my-header2`,`bar`)",
|
||||
RuleSyntax: "v3",
|
||||
},
|
||||
"default-http-app-1-my-gateway-web-aaba0f24fd26e1ca2276": {
|
||||
|
@ -1263,7 +1284,7 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-http-app-1-my-gateway-web-4a1b73e6f83804949a37-wrr": {
|
||||
"default-http-app-1-my-gateway-web-6cf37fa71907768d925c-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
|
@ -1733,9 +1754,27 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel}
|
||||
p := Provider{
|
||||
EntryPoints: test.entryPoints,
|
||||
ExperimentalChannel: test.experimentalChannel,
|
||||
}
|
||||
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -1992,12 +2031,28 @@ func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) {
|
|||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints}
|
||||
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
for group, kindFuncs := range test.groupKindBackendFuncs {
|
||||
for kind, backendFunc := range kindFuncs {
|
||||
p.RegisterBackendFuncs(group, kind, backendFunc)
|
||||
}
|
||||
}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -2208,12 +2263,28 @@ func TestLoadHTTPRoutes_filterExtensionRef(t *testing.T) {
|
|||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints}
|
||||
|
||||
k8sObjects, gwObjects := readResources(t, []string{"services.yml", "httproute/filter_extension_ref.yml"})
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
for group, kindFuncs := range test.groupKindFilterFuncs {
|
||||
for kind, filterFunc := range kindFuncs {
|
||||
p.RegisterFilterFuncs(group, kind, filterFunc)
|
||||
}
|
||||
}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock([]string{"services.yml", "httproute/filter_extension_ref.yml"}...))
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -2971,8 +3042,28 @@ func TestLoadTCPRoutes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: true}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
p := Provider{
|
||||
EntryPoints: test.entryPoints,
|
||||
ExperimentalChannel: true,
|
||||
}
|
||||
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
client.experimentalChannel = true
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -4099,8 +4190,28 @@ func TestLoadTLSRoutes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: true}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
p := Provider{
|
||||
EntryPoints: test.entryPoints,
|
||||
ExperimentalChannel: true,
|
||||
}
|
||||
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
client.experimentalChannel = true
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -5112,8 +5223,28 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
p := Provider{
|
||||
EntryPoints: test.entryPoints,
|
||||
ExperimentalChannel: test.experimentalChannel,
|
||||
}
|
||||
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
client.experimentalChannel = test.experimentalChannel
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -5303,8 +5434,28 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{EntryPoints: test.entryPoints, ExperimentalChannel: test.experimentalChannel}
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
|
||||
p := Provider{
|
||||
EntryPoints: test.entryPoints,
|
||||
ExperimentalChannel: test.experimentalChannel,
|
||||
}
|
||||
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
client.experimentalChannel = test.experimentalChannel
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
conf := p.loadConfigurationFromGateway(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -5731,7 +5882,8 @@ func Test_shouldAttach(t *testing.T) {
|
|||
listener gatev1.Listener
|
||||
routeNamespace string
|
||||
routeSpec gatev1.CommonRouteSpec
|
||||
expectedAttach bool
|
||||
wantAttach bool
|
||||
wantParentRef gatev1.ParentReference
|
||||
}{
|
||||
{
|
||||
desc: "No ParentRefs",
|
||||
|
@ -5748,7 +5900,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
routeSpec: gatev1.CommonRouteSpec{
|
||||
ParentRefs: nil,
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Unsupported Kind",
|
||||
|
@ -5773,7 +5925,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Unsupported Group",
|
||||
|
@ -5798,7 +5950,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Kind is nil",
|
||||
|
@ -5822,7 +5974,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Group is nil",
|
||||
|
@ -5846,7 +5998,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "SectionName does not match a listener desc",
|
||||
|
@ -5871,7 +6023,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Namespace does not match the Gateway namespace",
|
||||
|
@ -5896,7 +6048,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Route namespace does not match the Gateway namespace",
|
||||
|
@ -5920,7 +6072,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Unsupported Kind",
|
||||
|
@ -5945,7 +6097,7 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: false,
|
||||
wantAttach: false,
|
||||
},
|
||||
{
|
||||
desc: "Route namespace matches the Gateway namespace",
|
||||
|
@ -5969,7 +6121,13 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: true,
|
||||
wantAttach: true,
|
||||
wantParentRef: gatev1.ParentReference{
|
||||
SectionName: sectionNamePtr("foo"),
|
||||
Name: "gateway",
|
||||
Kind: kindPtr("Gateway"),
|
||||
Group: groupPtr(gatev1.GroupName),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Namespace matches the Gateway namespace",
|
||||
|
@ -5994,7 +6152,14 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: true,
|
||||
wantAttach: true,
|
||||
wantParentRef: gatev1.ParentReference{
|
||||
SectionName: sectionNamePtr("foo"),
|
||||
Name: "gateway",
|
||||
Namespace: namespacePtr("default"),
|
||||
Kind: kindPtr("Gateway"),
|
||||
Group: groupPtr(gatev1.GroupName),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Only one ParentRef matches the Gateway",
|
||||
|
@ -6024,7 +6189,13 @@ func Test_shouldAttach(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedAttach: true,
|
||||
wantAttach: true,
|
||||
wantParentRef: gatev1.ParentReference{
|
||||
Name: "gateway",
|
||||
Namespace: namespacePtr("default"),
|
||||
Kind: kindPtr("Gateway"),
|
||||
Group: groupPtr(gatev1.GroupName),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -6032,8 +6203,9 @@ func Test_shouldAttach(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := shouldAttach(test.gateway, test.listener, test.routeNamespace, test.routeSpec)
|
||||
assert.Equal(t, test.expectedAttach, got)
|
||||
gotParentRef, gotAttach := shouldAttach(test.gateway, test.listener, test.routeNamespace, test.routeSpec)
|
||||
assert.Equal(t, test.wantAttach, gotAttach)
|
||||
assert.Equal(t, test.wantParentRef, gotParentRef)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -6627,7 +6799,22 @@ func Test_gatewayAddresses(t *testing.T) {
|
|||
|
||||
p := Provider{StatusAddress: test.statusAddress}
|
||||
|
||||
got, err := p.gatewayAddresses(newClientMock(test.paths...))
|
||||
k8sObjects, gwObjects := readResources(t, test.paths)
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, gwClient)
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(gwObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
got, err := p.gatewayAddresses(client)
|
||||
test.wantErr(t, err)
|
||||
|
||||
assert.Equal(t, test.want, got)
|
||||
|
@ -6662,3 +6849,46 @@ func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { retu
|
|||
func objectNamePtr(objectName gatev1.ObjectName) *gatev1.ObjectName {
|
||||
return &objectName
|
||||
}
|
||||
|
||||
// We cannot use the gateway-api fake.NewSimpleClientset due to Gateway being pluralized as "gatewaies" instead of "gateways".
|
||||
func newGatewaySimpleClientSet(t *testing.T, objects ...runtime.Object) *gatefake.Clientset {
|
||||
t.Helper()
|
||||
|
||||
client := gatefake.NewSimpleClientset(objects...)
|
||||
for _, object := range objects {
|
||||
gateway, ok := object.(*gatev1.Gateway)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.GatewayV1().Gateways(gateway.Namespace).Create(context.Background(), gateway, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) {
|
||||
t.Helper()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var gwObjects []runtime.Object
|
||||
for _, path := range paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch obj.GetObjectKind().GroupVersionKind().Group {
|
||||
case "gateway.networking.k8s.io":
|
||||
gwObjects = append(gwObjects, obj)
|
||||
default:
|
||||
k8sObjects = append(k8sObjects, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return k8sObjects, gwObjects
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue