Merge branch v3.2 into master

This commit is contained in:
kevinpollet 2024-10-10 11:32:18 +02:00
commit 06e64af9e9
No known key found for this signature in database
GPG key ID: 0C9A5DDD1B292453
39 changed files with 1994 additions and 1638 deletions

View file

@ -1,6 +1,7 @@
package compress
import (
"cmp"
"slices"
"strconv"
"strings"
@ -19,7 +20,7 @@ const (
type Encoding struct {
Type string
Weight *float64
Weight float64
}
func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string {
@ -42,11 +43,11 @@ func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, sup
encoding := encodings[0]
if encoding.Type == identityName && encoding.Weight != nil && *encoding.Weight == 0 {
if encoding.Type == identityName && encoding.Weight == 0 {
return notAcceptable
}
if encoding.Type == wildcardName && encoding.Weight != nil && *encoding.Weight == 0 {
if encoding.Type == wildcardName && encoding.Weight == 0 {
return notAcceptable
}
@ -87,11 +88,13 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
continue
}
var weight *float64
// If no "q" parameter is present, the default weight is 1.
// https://www.rfc-editor.org/rfc/rfc9110.html#name-quality-values
weight := 1.0
if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") {
w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64)
weight = &w
weight = w
hasWeight = true
}
@ -102,41 +105,9 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
}
}
slices.SortFunc(encodings, compareEncoding)
slices.SortFunc(encodings, func(a, b Encoding) int {
return cmp.Compare(b.Weight, a.Weight)
})
return encodings, hasWeight
}
func compareEncoding(a, b Encoding) int {
lhs, rhs := a.Weight, b.Weight
if lhs == nil && rhs == nil {
return 0
}
if lhs == nil && *rhs == 0 {
return -1
}
if lhs == nil {
return 1
}
if rhs == nil && *lhs == 0 {
return 1
}
if rhs == nil {
return -1
}
if *lhs < *rhs {
return 1
}
if *lhs > *rhs {
return -1
}
return 0
}

View file

@ -87,6 +87,12 @@ func Test_getCompressionEncoding(t *testing.T) {
supportedEncodings: []string{zstdName, brotliName},
expected: brotliName,
},
{
desc: "mixed weight",
acceptEncoding: []string{"gzip, br;q=0.9"},
supportedEncodings: []string{gzipName, brotliName},
expected: gzipName,
},
}
for _, test := range testCases {
@ -116,10 +122,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "weight",
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: zstdName, Weight: ptr(0.9)},
{Type: gzipName, Weight: ptr(0.8)},
{Type: wildcardName, Weight: ptr(0.1)},
{Type: brotliName, Weight: 1},
{Type: zstdName, Weight: 0.9},
{Type: gzipName, Weight: 0.8},
{Type: wildcardName, Weight: 0.1},
},
assertWeight: assert.True,
},
@ -128,9 +134,9 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
supportedEncodings: []string{brotliName, gzipName},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: gzipName, Weight: ptr(0.8)},
{Type: wildcardName, Weight: ptr(0.1)},
{Type: brotliName, Weight: 1},
{Type: gzipName, Weight: 0.8},
{Type: wildcardName, Weight: 0.1},
},
assertWeight: assert.True,
},
@ -138,10 +144,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "mixed",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: zstdName},
{Type: gzipName},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: zstdName, Weight: 1},
{Type: gzipName, Weight: 1},
{Type: brotliName, Weight: 1},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -150,8 +156,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
supportedEncodings: []string{zstdName},
expected: []Encoding{
{Type: zstdName},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: zstdName, Weight: 1},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -159,10 +165,10 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "no weight",
values: []string{"zstd, gzip, br, *"},
expected: []Encoding{
{Type: zstdName},
{Type: gzipName},
{Type: brotliName},
{Type: wildcardName},
{Type: zstdName, Weight: 1},
{Type: gzipName, Weight: 1},
{Type: brotliName, Weight: 1},
{Type: wildcardName, Weight: 1},
},
assertWeight: assert.False,
},
@ -171,8 +177,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"zstd, gzip, br, *"},
supportedEncodings: []string{"gzip"},
expected: []Encoding{
{Type: gzipName},
{Type: wildcardName},
{Type: gzipName, Weight: 1},
{Type: wildcardName, Weight: 1},
},
assertWeight: assert.False,
},
@ -180,9 +186,9 @@ func Test_parseAcceptEncoding(t *testing.T) {
desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
expected: []Encoding{
{Type: gzipName, Weight: ptr[float64](1)},
{Type: identityName, Weight: ptr(0.5)},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: gzipName, Weight: 1},
{Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -191,8 +197,8 @@ func Test_parseAcceptEncoding(t *testing.T) {
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
supportedEncodings: []string{"br"},
expected: []Encoding{
{Type: identityName, Weight: ptr(0.5)},
{Type: wildcardName, Weight: ptr[float64](0)},
{Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: 0},
},
assertWeight: assert.True,
},
@ -213,7 +219,3 @@ func Test_parseAcceptEncoding(t *testing.T) {
})
}
}
func ptr[T any](t T) *T {
return &t
}

View file

@ -688,39 +688,32 @@ func Test1xxResponses(t *testing.T) {
assert.NotEqualValues(t, body, fakeBody)
}
func BenchmarkCompress(b *testing.B) {
func BenchmarkCompressGzip(b *testing.B) {
runCompressionBenchmark(b, gzipName)
}
func BenchmarkCompressBrotli(b *testing.B) {
runCompressionBenchmark(b, brotliName)
}
func BenchmarkCompressZstandard(b *testing.B) {
runCompressionBenchmark(b, zstdName)
}
func runCompressionBenchmark(b *testing.B, algorithm string) {
b.Helper()
testCases := []struct {
name string
parallel bool
size int
}{
{
name: "2k",
size: 2048,
},
{
name: "20k",
size: 20480,
},
{
name: "100k",
size: 102400,
},
{
name: "2k parallel",
parallel: true,
size: 2048,
},
{
name: "20k parallel",
parallel: true,
size: 20480,
},
{
name: "100k parallel",
parallel: true,
size: 102400,
},
{"2k", false, 2048},
{"20k", false, 20480},
{"100k", false, 102400},
{"2k parallel", true, 2048},
{"20k parallel", true, 20480},
{"100k parallel", true, 102400},
}
for _, test := range testCases {
@ -734,7 +727,7 @@ func BenchmarkCompress(b *testing.B) {
handler, _ := New(context.Background(), next, dynamic.Compress{}, "testing")
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set("Accept-Encoding", "gzip")
req.Header.Set("Accept-Encoding", algorithm)
b.ReportAllocs()
b.SetBytes(int64(test.size))
@ -742,7 +735,7 @@ func BenchmarkCompress(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
runBenchmark(b, req, handler)
runBenchmark(b, req, handler, algorithm)
}
})
return
@ -750,13 +743,13 @@ func BenchmarkCompress(b *testing.B) {
b.ResetTimer()
for range b.N {
runBenchmark(b, req, handler)
runBenchmark(b, req, handler, algorithm)
}
})
}
}
func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
func runBenchmark(b *testing.B, req *http.Request, handler http.Handler, algorithm string) {
b.Helper()
res := httptest.NewRecorder()
@ -765,7 +758,7 @@ func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
b.Fatalf("Expected 200 but got %d", code)
}
assert.Equal(b, gzipName, res.Header().Get(contentEncodingHeader))
assert.Equal(b, algorithm, res.Header().Get(contentEncodingHeader))
}
func generateBytes(length int) []byte {

View file

@ -8,6 +8,7 @@ import (
"mime"
"net"
"net/http"
"sync"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/zstd"
@ -45,6 +46,7 @@ type CompressionHandler struct {
excludedContentTypes []parsedContentType
includedContentTypes []parsedContentType
next http.Handler
writerPool sync.Pool
}
// NewCompressionHandler returns a new compressing handler.
@ -92,7 +94,7 @@ func NewCompressionHandler(cfg Config, next http.Handler) (http.Handler, error)
func (c *CompressionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add(vary, acceptEncoding)
compressionWriter, err := newCompressionWriter(c.cfg.Algorithm, rw)
compressionWriter, err := c.getCompressionWriter(rw)
if err != nil {
logger := middlewares.GetLogger(r.Context(), c.cfg.MiddlewareName, typeName)
logger.Debug().Msgf("Create compression handler: %v", err)
@ -100,6 +102,7 @@ func (c *CompressionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
rw.WriteHeader(http.StatusInternalServerError)
return
}
defer c.putCompressionWriter(compressionWriter)
responseWriter := &responseWriter{
rw: rw,
@ -130,6 +133,8 @@ type compression interface {
// as it would otherwise send some extra "end of compression" bytes.
// Close also makes sure to flush whatever was left to write from the buffer.
Close() error
// Reset reinitializes the state of the encoder, allowing it to be reused.
Reset(w io.Writer)
}
type compressionWriter struct {
@ -137,6 +142,19 @@ type compressionWriter struct {
alg string
}
func (c *CompressionHandler) getCompressionWriter(rw io.Writer) (*compressionWriter, error) {
if writer, ok := c.writerPool.Get().(*compressionWriter); ok {
writer.compression.Reset(rw)
return writer, nil
}
return newCompressionWriter(c.cfg.Algorithm, rw)
}
func (c *CompressionHandler) putCompressionWriter(writer *compressionWriter) {
writer.Reset(nil)
c.writerPool.Put(writer)
}
func newCompressionWriter(algo string, in io.Writer) (*compressionWriter, error) {
switch algo {
case brotliName:

View file

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"github.com/http-wasm/http-wasm-host-go/handler"
@ -135,7 +136,19 @@ func (b *wasmMiddlewareBuilder) buildMiddleware(ctx context.Context, next http.H
return nil, nil, fmt.Errorf("creating middleware: %w", err)
}
return mw.NewHandler(ctx, next), applyCtx, nil
h := mw.NewHandler(ctx, next)
// Traefik does not Close the middleware when creating a new instance on a configuration change.
// When the middleware is marked to be GC, we need to close it so the wasm instance is properly closed.
// Reference: https://github.com/traefik/traefik/issues/11119
runtime.SetFinalizer(h, func(_ http.Handler) {
if err := mw.Close(ctx); err != nil {
logger.Err(err).Msg("[wasm] middleware Close failed")
} else {
logger.Debug().Msg("[wasm] middleware Close ok")
}
})
return h, applyCtx, nil
}
// WasmMiddleware is an HTTP handler plugin wrapper.

View file

@ -0,0 +1,51 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: traefik-internal
labels:
name: traefik-internal
spec:
controllerName: traefik.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik-internal
namespace: default
spec:
gatewayClassName: traefik-internal
listeners:
- name: http
protocol: HTTP
port: 9080
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: traefik-external
labels:
name: traefik-external
spec:
controllerName: traefik.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: traefik-external
namespace: default
spec:
gatewayClassName: traefik-external
listeners:
- name: http
protocol: HTTP
port: 9080
allowedRoutes:
namespaces:
from: Same

View file

@ -96,6 +96,7 @@ spec:
- name: web
protocol: TCP
port: 8080
appProtocol: http
targetPort: web
selector:
app: containous
@ -131,6 +132,8 @@ metadata:
spec:
ports:
- name: websecure
protocol: TCP
appProtocol: https
port: 443
targetPort: websecure
selector:

View file

@ -2,6 +2,7 @@ package gateway
import (
"context"
"errors"
"fmt"
"net"
"strconv"
@ -32,6 +33,11 @@ func (p *Provider) loadGRPCRoutes(ctx context.Context, gatewayListeners []gatewa
Str("namespace", route.Namespace).
Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1.RouteParentStatus{
@ -48,11 +54,9 @@ func (p *Provider) loadGRPCRoutes(ctx context.Context, gatewayListeners []gatewa
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindGRPCRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false
@ -334,14 +338,15 @@ func (p *Provider) loadGRPCServers(namespace string, route *gatev1.GRPCRoute, ba
}
}
if svcPort.AppProtocol != nil && *svcPort.AppProtocol != appProtocolH2C {
protocol, err := getGRPCServiceProtocol(svcPort)
if err != nil {
return nil, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: route.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.RouteReasonUnsupportedProtocol),
Message: fmt.Sprintf("Cannot load GRPCBackendRef %s/%s: only kubernetes.io/h2c appProtocol is supported", namespace, backendRef.Name),
Message: fmt.Sprintf("Cannot load GRPCBackendRef %s/%s: only \"kubernetes.io/h2c\" and \"https\" appProtocol is supported", namespace, backendRef.Name),
}
}
@ -350,7 +355,7 @@ func (p *Provider) loadGRPCServers(namespace string, route *gatev1.GRPCRoute, ba
for _, ba := range backendAddresses {
lb.Servers = append(lb.Servers, dynamic.Server{
URL: fmt.Sprintf("h2c://%s", net.JoinHostPort(ba.IP, strconv.Itoa(int(ba.Port)))),
URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ba.IP, strconv.Itoa(int(ba.Port)))),
})
}
return lb, nil
@ -405,3 +410,22 @@ func buildGRPCHeaderRules(headers []gatev1.GRPCHeaderMatch) []string {
return rules
}
func getGRPCServiceProtocol(portSpec corev1.ServicePort) (string, error) {
if portSpec.Protocol != corev1.ProtocolTCP {
return "", errors.New("only TCP protocol is supported")
}
if portSpec.AppProtocol == nil {
return schemeH2C, nil
}
switch ap := *portSpec.AppProtocol; ap {
case appProtocolH2C:
return schemeH2C, nil
case appProtocolHTTPS:
return schemeHTTPS, nil
default:
return "", fmt.Errorf("unsupported application protocol %s", ap)
}
}

View file

@ -36,6 +36,11 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewa
Str("namespace", route.Namespace).
Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1.RouteParentStatus{
@ -52,11 +57,9 @@ func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewa
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindHTTPRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false
@ -465,7 +468,7 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba
}
}
protocol, err := getProtocol(svcPort)
protocol, err := getHTTPServiceProtocol(svcPort)
if err != nil {
return nil, corev1.ServicePort{}, &metav1.Condition{
Type: string(gatev1.RouteConditionResolvedRefs),
@ -718,7 +721,7 @@ func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch g
var port *string
filterScheme := ptr.Deref(filter.Scheme, "")
if filterScheme == "http" || filterScheme == "https" {
if filterScheme == schemeHTTP || filterScheme == schemeHTTPS {
port = ptr.To("")
}
if filter.Port != nil {
@ -780,26 +783,26 @@ func createURLRewrite(filter *gatev1.HTTPURLRewriteFilter, pathMatch gatev1.HTTP
}, nil
}
func getProtocol(portSpec corev1.ServicePort) (string, error) {
func getHTTPServiceProtocol(portSpec corev1.ServicePort) (string, error) {
if portSpec.Protocol != corev1.ProtocolTCP {
return "", errors.New("only TCP protocol is supported")
}
if portSpec.AppProtocol == nil {
protocol := "http"
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
protocol = "https"
protocol := schemeHTTP
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, schemeHTTPS) {
protocol = schemeHTTPS
}
return protocol, nil
}
switch ap := *portSpec.AppProtocol; ap {
case appProtocolH2C:
return "h2c", nil
case appProtocolWS:
return "http", nil
case appProtocolWSS:
return "https", nil
return schemeH2C, nil
case appProtocolHTTP, appProtocolWS:
return schemeHTTP, nil
case appProtocolHTTPS, appProtocolWSS:
return schemeHTTPS, nil
default:
return "", fmt.Errorf("unsupported application protocol %s", ap)
}

View file

@ -50,9 +50,15 @@ const (
kindTLSRoute = "TLSRoute"
kindService = "Service"
appProtocolH2C = "kubernetes.io/h2c"
appProtocolWS = "kubernetes.io/ws"
appProtocolWSS = "kubernetes.io/wss"
appProtocolHTTP = "http"
appProtocolHTTPS = "https"
appProtocolH2C = "kubernetes.io/h2c"
appProtocolWS = "kubernetes.io/ws"
appProtocolWSS = "kubernetes.io/wss"
schemeHTTP = "http"
schemeHTTPS = "https"
schemeH2C = "h2c"
)
// Provider holds configurations of the provider.
@ -357,7 +363,13 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
}
}
gateways := p.client.ListGateways()
var gateways []*gatev1.Gateway
for _, gateway := range p.client.ListGateways() {
if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
continue
}
gateways = append(gateways, gateway)
}
var gatewayListeners []gatewayListener
for _, gateway := range gateways {
@ -366,10 +378,6 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C
Str("namespace", gateway.Namespace).
Logger()
if _, ok := gatewayClassNames[string(gateway.Spec.GatewayClassName)]; !ok {
continue
}
gatewayListeners = append(gatewayListeners, p.loadGatewayListeners(logger.WithContext(ctx), gateway, conf)...)
}
@ -1117,24 +1125,36 @@ func allowRoute(listener gatewayListener, routeNamespace, routeKind string) bool
})
}
func matchListener(listener gatewayListener, routeNamespace string, parentRef gatev1.ParentReference) bool {
if ptr.Deref(parentRef.Group, gatev1.GroupName) != gatev1.GroupName {
return false
func matchingGatewayListeners(gatewayListeners []gatewayListener, routeNamespace string, parentRefs []gatev1.ParentReference) []gatewayListener {
var listeners []gatewayListener
for _, listener := range gatewayListeners {
for _, parentRef := range parentRefs {
if ptr.Deref(parentRef.Group, gatev1.GroupName) != gatev1.GroupName {
continue
}
if ptr.Deref(parentRef.Kind, kindGateway) != kindGateway {
continue
}
parentRefNamespace := string(ptr.Deref(parentRef.Namespace, gatev1.Namespace(routeNamespace)))
if listener.GWNamespace != parentRefNamespace {
continue
}
if string(parentRef.Name) != listener.GWName {
continue
}
listeners = append(listeners, listener)
}
}
if ptr.Deref(parentRef.Kind, kindGateway) != kindGateway {
return false
}
parentRefNamespace := string(ptr.Deref(parentRef.Namespace, gatev1.Namespace(routeNamespace)))
if listener.GWNamespace != parentRefNamespace {
return false
}
if string(parentRef.Name) != listener.GWName {
return false
}
return listeners
}
func matchListener(listener gatewayListener, parentRef gatev1.ParentReference) bool {
sectionName := string(ptr.Deref(parentRef.SectionName, ""))
if sectionName != "" && sectionName != listener.Name {
return false

View file

@ -49,6 +49,47 @@ func init() {
}
}
func TestGatewayClassLabelSelector(t *testing.T) {
k8sObjects, gwObjects := readResources(t, []string{"gatewayclass_labelselector.yaml"})
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
gwClient := newGatewaySimpleClientSet(t, gwObjects...)
client := newClientImpl(kubeClient, gwClient)
// This is initialized by the Provider init method but this cannot be called in a unit test.
client.labelSelector = "name=traefik-internal"
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
}
p := Provider{
EntryPoints: map[string]Entrypoint{"http": {Address: ":9080"}},
StatusAddress: &StatusAddress{IP: "1.2.3.4"},
client: client,
}
_ = p.loadConfigurationFromGateways(context.Background())
gw, err := gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-external", metav1.GetOptions{})
require.NoError(t, err)
assert.Empty(t, gw.Status.Addresses)
gw, err = gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-internal", metav1.GetOptions{})
require.NoError(t, err)
require.Len(t, gw.Status.Addresses, 1)
require.NotNil(t, gw.Status.Addresses[0].Type)
assert.Equal(t, gatev1.IPAddressType, *gw.Status.Addresses[0].Type)
assert.Equal(t, "1.2.3.4", gw.Status.Addresses[0].Value)
}
func TestLoadHTTPRoutes(t *testing.T) {
testCases := []struct {
desc string
@ -6738,127 +6779,171 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) {
}
}
func Test_matchListener(t *testing.T) {
func Test_matchingGatewayListener(t *testing.T) {
testCases := []struct {
desc string
gwListener gatewayListener
parentRef gatev1.ParentReference
gwListeners []gatewayListener
parentRefs []gatev1.ParentReference
routeNamespace string
wantMatch bool
wantLen int
}{
{
desc: "Unsupported group",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group("foo")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Unsupported kind",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("foo")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Namespace does not match the listener",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Namespace: ptr.To(gatev1.Namespace("foo")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Route namespace defaulting does not match the listener",
gwListener: gatewayListener{
gwListeners: []gatewayListener{{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
}},
routeNamespace: "foo",
parentRef: gatev1.ParentReference{
parentRefs: []gatev1.ParentReference{{
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Name does not match the listener",
gwListener: gatewayListener{
Name: "foo",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
}},
parentRefs: []gatev1.ParentReference{{
Namespace: ptr.To(gatev1.Namespace("default")),
Name: "foo",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
},
{
desc: "SectionName does not match a listener",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("bar")),
Name: "gateway",
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
},
wantMatch: false,
}},
wantLen: 0,
},
{
desc: "Match",
gwListener: gatewayListener{
Name: "foo",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
}},
parentRefs: []gatev1.ParentReference{{
Name: "gateway",
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 1,
},
{
desc: "Match with route namespace defaulting",
gwListeners: []gatewayListener{{
GWName: "gateway",
GWNamespace: "default",
}},
routeNamespace: "default",
parentRefs: []gatev1.ParentReference{{
Name: "gateway",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
}},
wantLen: 1,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
listeners := matchingGatewayListeners(test.gwListeners, test.routeNamespace, test.parentRefs)
assert.Len(t, listeners, test.wantLen)
})
}
}
func Test_matchListener(t *testing.T) {
testCases := []struct {
desc string
gwListener gatewayListener
parentRef gatev1.ParentReference
wantMatch bool
}{
{
desc: "Section do not match",
gwListener: gatewayListener{
Name: "foo",
Port: gatev1.PortNumber(80),
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("bar")),
Port: ptr.To(gatev1.PortNumber(80)),
},
},
{
desc: "Section matches",
gwListener: gatewayListener{
Name: "foo",
Port: gatev1.PortNumber(80),
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")),
Name: "gateway",
Namespace: ptr.To(gatev1.Namespace("default")),
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
Port: ptr.To(gatev1.PortNumber(80)),
},
wantMatch: true,
},
{
desc: "Match with route namespace defaulting",
desc: "Port do not match",
gwListener: gatewayListener{
Name: "foo",
GWName: "gateway",
GWNamespace: "default",
Name: "foo",
Port: gatev1.PortNumber(90),
},
routeNamespace: "default",
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")),
Name: "gateway",
Group: ptr.To(gatev1.Group(gatev1.GroupName)),
Kind: ptr.To(gatev1.Kind("Gateway")),
Port: ptr.To(gatev1.PortNumber(80)),
},
},
{
desc: "Port matches",
gwListener: gatewayListener{
Name: "foo",
Port: gatev1.PortNumber(80),
},
parentRef: gatev1.ParentReference{
SectionName: ptr.To(gatev1.SectionName("foo")),
Port: ptr.To(gatev1.PortNumber(80)),
},
wantMatch: true,
},
@ -6868,7 +6953,7 @@ func Test_matchListener(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
gotMatch := matchListener(test.gwListener, test.routeNamespace, test.parentRef)
gotMatch := matchListener(test.gwListener, test.parentRef)
assert.Equal(t, test.wantMatch, gotMatch)
})
}

View file

@ -32,6 +32,11 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gatewayListeners []gateway
Str("namespace", route.Namespace).
Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1alpha2.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1alpha2.RouteParentStatus{
@ -48,11 +53,9 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gatewayListeners []gateway
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindTCPRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false

View file

@ -32,6 +32,11 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gateway
Str("tls_route", route.Name).
Str("namespace", route.Namespace).Logger()
routeListeners := matchingGatewayListeners(gatewayListeners, route.Namespace, route.Spec.ParentRefs)
if len(routeListeners) == 0 {
continue
}
var parentStatuses []gatev1alpha2.RouteParentStatus
for _, parentRef := range route.Spec.ParentRefs {
parentStatus := &gatev1alpha2.RouteParentStatus{
@ -48,11 +53,9 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gatewayListeners []gateway
},
}
for _, listener := range gatewayListeners {
accepted := true
if !matchListener(listener, route.Namespace, parentRef) {
accepted = false
}
for _, listener := range routeListeners {
accepted := matchListener(listener, parentRef)
if accepted && !allowRoute(listener, route.Namespace, kindTLSRoute) {
parentStatus.Conditions = updateRouteConditionAccepted(parentStatus.Conditions, string(gatev1.RouteReasonNotAllowedByListeners))
accepted = false

View file

@ -117,6 +117,9 @@ func TestConnPool_MaxIdleConn(t *testing.T) {
}
func TestGC(t *testing.T) {
// TODO: make the test stable if possible.
t.Skip("This test is flaky")
var isDestroyed bool
pools := map[string]*connPool{}
dialer := func() (net.Conn, error) {