Set Gateway HTTPRoute status

Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
Kevin Pollet 2024-05-01 06:38:03 +02:00 committed by GitHub
parent 9d8fd24730
commit 05d2c86074
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 995 additions and 883 deletions

View file

@ -195,40 +195,28 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
LatestObservedGenerationSet: 5 * time.Second,
RequiredConsecutiveSuccesses: 0,
},
SupportedFeatures: sets.New[ksuite.SupportedFeature]().
Insert(ksuite.GatewayCoreFeatures.UnsortedList()...).
Insert(ksuite.ReferenceGrantCoreFeatures.UnsortedList()...),
EnableAllSupportedFeatures: false,
RunTest: *k8sConformanceRunTest,
// Until the feature are all supported, following tests are skipped.
SkipTests: []string{
"HTTPExactPathMatching",
"HTTPRouteHostnameIntersection",
"HTTPRouteListenerHostnameMatching",
"HTTPRouteRequestHeaderModifier",
"GatewayClassObservedGenerationBump",
"HTTPRouteInvalidNonExistentBackendRef",
"GatewayWithAttachedRoutes",
"HTTPRouteCrossNamespace",
"HTTPRouteDisallowedKind",
"HTTPRouteInvalidReferenceGrant",
"HTTPRouteObservedGenerationBump",
"TLSRouteSimpleSameNamespace",
"TLSRouteInvalidReferenceGrant",
"HTTPRouteInvalidCrossNamespaceParentRef",
"HTTPRouteInvalidParentRefNotMatchingSectionName",
"GatewayModifyListeners",
"GatewayInvalidTLSConfiguration",
"HTTPRouteInvalidCrossNamespaceBackendRef",
"HTTPRouteMatchingAcrossRoutes",
"HTTPRoutePartiallyInvalidViaInvalidReferenceGrant",
"HTTPRouteRedirectHostAndStatus",
"HTTPRouteInvalidBackendRefUnknownKind",
"HTTPRoutePathMatchOrder",
"HTTPRouteSimpleSameNamespace",
"HTTPRouteMatching",
"HTTPRouteHeaderMatching",
"HTTPRouteReferenceGrant",
tests.GatewayClassObservedGenerationBump.ShortName,
tests.GatewayWithAttachedRoutes.ShortName,
tests.GatewayModifyListeners.ShortName,
tests.GatewayInvalidTLSConfiguration.ShortName,
tests.HTTPRouteHostnameIntersection.ShortName,
tests.HTTPRouteListenerHostnameMatching.ShortName,
tests.HTTPRouteInvalidNonExistentBackendRef.ShortName,
tests.HTTPRouteInvalidReferenceGrant.ShortName,
tests.HTTPRouteInvalidCrossNamespaceParentRef.ShortName,
tests.HTTPRouteInvalidParentRefNotMatchingSectionName.ShortName,
tests.HTTPRouteInvalidCrossNamespaceBackendRef.ShortName,
tests.HTTPRouteMatchingAcrossRoutes.ShortName,
tests.HTTPRoutePartiallyInvalidViaInvalidReferenceGrant.ShortName,
tests.HTTPRouteRedirectHostAndStatus.ShortName,
tests.HTTPRouteInvalidBackendRefUnknownKind.ShortName,
tests.HTTPRoutePathMatchOrder.ShortName,
tests.HTTPRouteHeaderMatching.ShortName,
tests.HTTPRouteReferenceGrant.ShortName,
},
}
@ -241,10 +229,7 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
Version: version.Version,
Contact: []string{"@traefik/maintainers"},
},
ConformanceProfiles: sets.New[ksuite.ConformanceProfileName](
ksuite.HTTPConformanceProfileName,
ksuite.TLSConformanceProfileName,
),
ConformanceProfiles: sets.New(ksuite.HTTPConformanceProfileName),
})
require.NoError(s.T(), err)

View file

@ -14,6 +14,7 @@ import (
kerror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
ktypes "k8s.io/apimachinery/pkg/types"
kinformers "k8s.io/client-go/informers"
kclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@ -51,6 +52,7 @@ type Client interface {
GetGatewayClasses() ([]*gatev1.GatewayClass, error)
UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error
UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error
UpdateHTTPRouteStatus(ctx context.Context, gateway *gatev1.Gateway, nsName ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error
GetGateways() []*gatev1.Gateway
GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error)
GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error)
@ -384,7 +386,7 @@ func (c *clientWrapper) GetGateways() []*gatev1.Gateway {
var result []*gatev1.Gateway
for ns, factory := range c.factoriesGateway {
gateways, err := factory.Gateway().V1().Gateways().Lister().List(labels.Everything())
gateways, err := factory.Gateway().V1().Gateways().Lister().Gateways(ns).List(labels.Everything())
if err != nil {
log.Error().Err(err).Msgf("Failed to list Gateways in namespace %s", ns)
continue
@ -453,6 +455,46 @@ func (c *clientWrapper) UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStat
return nil
}
func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, gateway *gatev1.Gateway, nsName ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error {
if !c.isWatchedNamespace(nsName.Namespace) {
return fmt.Errorf("updating HTTPRoute status %s/%s: namespace is not within watched namespaces", nsName.Namespace, nsName.Name)
}
route, err := c.factoriesGateway[c.lookupNamespace(nsName.Namespace)].Gateway().V1().HTTPRoutes().Lister().HTTPRoutes(nsName.Namespace).Get(nsName.Name)
if err != nil {
return fmt.Errorf("getting HTTPRoute %s/%s: %w", nsName.Namespace, nsName.Name, err)
}
var statuses []gatev1.RouteParentStatus
for _, status := range route.Status.Parents {
if status.ControllerName != controllerName {
statuses = append(statuses, status)
continue
}
if status.ParentRef.Namespace != nil && string(*status.ParentRef.Namespace) != gateway.Namespace {
statuses = append(statuses, status)
continue
}
if string(status.ParentRef.Name) != gateway.Name {
statuses = append(statuses, status)
continue
}
}
statuses = append(statuses, status.Parents...)
route = route.DeepCopy()
route.Status = gatev1.HTTPRouteStatus{
RouteStatus: gatev1.RouteStatus{
Parents: statuses,
},
}
if _, err := c.csGateway.GatewayV1().HTTPRoutes(nsName.Namespace).UpdateStatus(ctx, route, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("updating HTTPRoute %s/%s status: %w", nsName.Namespace, nsName.Name, err)
}
return nil
}
func statusEquals(oldStatus, newStatus gatev1.GatewayStatus) bool {
if len(oldStatus.Listeners) != len(newStatus.Listeners) {
return false

View file

@ -1,254 +0,0 @@
package gateway
import (
"fmt"
"os"
"path/filepath"
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
kscheme "k8s.io/client-go/kubernetes/scheme"
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
var _ Client = (*clientMock)(nil)
func init() {
// required by k8s.MustParseYaml
err := gatev1alpha2.AddToScheme(kscheme.Scheme)
if err != nil {
panic(err)
}
err = gatev1beta1.AddToScheme(kscheme.Scheme)
if err != nil {
panic(err)
}
err = gatev1.AddToScheme(kscheme.Scheme)
if err != nil {
panic(err)
}
}
type clientMock struct {
services []*corev1.Service
secrets []*corev1.Secret
endpoints []*corev1.Endpoints
namespaces []*corev1.Namespace
apiServiceError error
apiSecretError error
apiEndpointsError error
gatewayClasses []*gatev1.GatewayClass
gateways []*gatev1.Gateway
httpRoutes []*gatev1.HTTPRoute
tcpRoutes []*gatev1alpha2.TCPRoute
tlsRoutes []*gatev1alpha2.TLSRoute
referenceGrants []*gatev1beta1.ReferenceGrant
watchChan chan interface{}
}
func newClientMock(paths ...string) clientMock {
var c clientMock
for _, path := range paths {
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
if err != nil {
panic(err)
}
k8sObjects := k8s.MustParseYaml(yamlContent)
for _, obj := range k8sObjects {
switch o := obj.(type) {
case *corev1.Service:
c.services = append(c.services, o)
case *corev1.Secret:
c.secrets = append(c.secrets, o)
case *corev1.Namespace:
c.namespaces = append(c.namespaces, o)
case *corev1.Endpoints:
c.endpoints = append(c.endpoints, o)
case *gatev1.GatewayClass:
c.gatewayClasses = append(c.gatewayClasses, o)
case *gatev1.Gateway:
c.gateways = append(c.gateways, o)
case *gatev1.HTTPRoute:
c.httpRoutes = append(c.httpRoutes, o)
case *gatev1alpha2.TCPRoute:
c.tcpRoutes = append(c.tcpRoutes, o)
case *gatev1alpha2.TLSRoute:
c.tlsRoutes = append(c.tlsRoutes, o)
case *gatev1beta1.ReferenceGrant:
c.referenceGrants = append(c.referenceGrants, o)
default:
panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o))
}
}
}
return c
}
func (c clientMock) UpdateGatewayStatus(gateway *gatev1.Gateway, gatewayStatus gatev1.GatewayStatus) error {
for _, g := range c.gateways {
if g.Name == gateway.Name {
if !statusEquals(g.Status, gatewayStatus) {
g.Status = gatewayStatus
return nil
}
return fmt.Errorf("cannot update gateway %v", gateway.Name)
}
}
return nil
}
func (c clientMock) UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayClass, condition metav1.Condition) error {
for _, gc := range c.gatewayClasses {
if gc.Name == gatewayClass.Name {
for _, c := range gc.Status.Conditions {
if c.Type == condition.Type && c.Status != condition.Status {
c.Status = condition.Status
c.LastTransitionTime = condition.LastTransitionTime
c.Message = condition.Message
c.Reason = condition.Reason
}
}
}
}
return nil
}
func (c clientMock) UpdateGatewayStatusConditions(gateway *gatev1.Gateway, condition metav1.Condition) error {
for _, g := range c.gatewayClasses {
if g.Name == gateway.Name {
for _, c := range g.Status.Conditions {
if c.Type == condition.Type && (c.Status != condition.Status || c.Reason != condition.Reason) {
c.Status = condition.Status
c.LastTransitionTime = condition.LastTransitionTime
c.Message = condition.Message
c.Reason = condition.Reason
}
}
}
}
return nil
}
func (c clientMock) GetGatewayClasses() ([]*gatev1.GatewayClass, error) {
return c.gatewayClasses, nil
}
func (c clientMock) GetGateways() []*gatev1.Gateway {
return c.gateways
}
func inNamespace(m metav1.ObjectMeta, s string) bool {
return s == metav1.NamespaceAll || m.Namespace == s
}
func (c clientMock) GetNamespaces(selector labels.Selector) ([]string, error) {
var ns []string
for _, namespace := range c.namespaces {
if selector.Matches(labels.Set(namespace.Labels)) {
ns = append(ns, namespace.Name)
}
}
return ns, nil
}
func (c clientMock) GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) {
var httpRoutes []*gatev1.HTTPRoute
for _, namespace := range namespaces {
for _, httpRoute := range c.httpRoutes {
if inNamespace(httpRoute.ObjectMeta, namespace) {
httpRoutes = append(httpRoutes, httpRoute)
}
}
}
return httpRoutes, nil
}
func (c clientMock) GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error) {
var tcpRoutes []*gatev1alpha2.TCPRoute
for _, namespace := range namespaces {
for _, tcpRoute := range c.tcpRoutes {
if inNamespace(tcpRoute.ObjectMeta, namespace) {
tcpRoutes = append(tcpRoutes, tcpRoute)
}
}
}
return tcpRoutes, nil
}
func (c clientMock) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error) {
var tlsRoutes []*gatev1alpha2.TLSRoute
for _, namespace := range namespaces {
for _, tlsRoute := range c.tlsRoutes {
if inNamespace(tlsRoute.ObjectMeta, namespace) {
tlsRoutes = append(tlsRoutes, tlsRoute)
}
}
}
return tlsRoutes, nil
}
func (c clientMock) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
var referenceGrants []*gatev1beta1.ReferenceGrant
for _, referenceGrant := range c.referenceGrants {
if inNamespace(referenceGrant.ObjectMeta, namespace) {
referenceGrants = append(referenceGrants, referenceGrant)
}
}
return referenceGrants, nil
}
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
if c.apiServiceError != nil {
return nil, false, c.apiServiceError
}
for _, service := range c.services {
if inNamespace(service.ObjectMeta, namespace) && service.Name == name {
return service, true, nil
}
}
return nil, false, c.apiServiceError
}
func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
if c.apiEndpointsError != nil {
return nil, false, c.apiEndpointsError
}
for _, endpoints := range c.endpoints {
if inNamespace(endpoints.ObjectMeta, namespace) && endpoints.Name == name {
return endpoints, true, nil
}
}
return &corev1.Endpoints{}, false, nil
}
func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, error) {
if c.apiSecretError != nil {
return nil, false, c.apiSecretError
}
for _, secret := range c.secrets {
if inNamespace(secret.ObjectMeta, namespace) && secret.Name == name {
return secret, true, nil
}
}
return nil, false, nil
}
func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
return c.watchChan, nil
}

View file

@ -17,17 +17,6 @@ data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1

File diff suppressed because it is too large Load diff

View file

@ -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
}