mirror of
https://github.com/traefik/traefik.git
synced 2026-10-28 08:36:10 +08:00
Merge branch v3.7 into master
Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
34ea85d945
18 files changed
+991
-1330
No files matched your search
@@ -93,6 +93,7 @@ linters:
|
||||
- github.com/jaguilar/vt100
|
||||
- github.com/cucumber/godog
|
||||
- github.com/vulcand/oxy/v2
|
||||
- software.sslmate.com/src/go-pkcs12
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
|
||||
@@ -108,6 +108,18 @@ Dashboards, alerts and API queries relying on the previous names must be updated
|
||||
The routers and middlewares generated on the HTTP entry points for an Ingress carrying the `nginx.ingress.kubernetes.io/ssl-passthrough` annotation are renamed as well.
|
||||
They are now named after the Ingress rule and path they come from, `<namespace>-<ingress>-rule-<rule index>-path-<path index>` instead of `<namespace>-<ingress>-<host>-http`.
|
||||
|
||||
### Kubernetes Gateway API Provider: routes that match a request equally well
|
||||
|
||||
Until `v3.7.14`, two routes on the same listener could give routers with the very same rule.
|
||||
This occurs, for example, with a route that has no host name and a route that has the host name of the listener.
|
||||
One of the two routes served the requests, but you could not know which one.
|
||||
The route could also change at each reload of the configuration.
|
||||
|
||||
Starting with `v3.7.14`, only the route that has precedence serves the requests.
|
||||
The specification gives precedence to the older route, then to the first route in alphabetical order by `{namespace}/{name}`.
|
||||
Traefik does not create the routers of the other routes.
|
||||
Traefik writes a warning in the log, and gives the name of the router that serves the rule.
|
||||
|
||||
---
|
||||
|
||||
## v3.7.13
|
||||
|
||||
@@ -23,7 +23,7 @@ require (
|
||||
github.com/docker/go-connections v0.8.1
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/go-acme/lego/v5 v5.5.1
|
||||
github.com/go-acme/lego/v5 v5.5.2
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/golang/protobuf v1.5.4
|
||||
@@ -71,7 +71,7 @@ require (
|
||||
github.com/testcontainers/testcontainers-go/modules/k3s v0.42.0
|
||||
github.com/tetratelabs/wazero v1.8.0
|
||||
github.com/tidwall/gjson v1.17.0
|
||||
github.com/traefik/grpc-web v0.16.0
|
||||
github.com/traefik/grpc-web v0.17.0
|
||||
github.com/traefik/paerser v0.2.2
|
||||
github.com/traefik/traefik/dynamic/ext v0.0.0-00010101000000-000000000000
|
||||
github.com/traefik/yaegi v0.16.1
|
||||
@@ -175,10 +175,10 @@ require (
|
||||
github.com/bodgit/gssapi v0.0.4 // indirect
|
||||
github.com/bodgit/tsig v1.3.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.15.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/coder/websocket v1.8.15 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.1 // indirect
|
||||
@@ -199,7 +199,6 @@ require (
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-acme/alidns-20150109/v5 v5.6.1 // indirect
|
||||
github.com/go-acme/esa-20240910/v3 v3.13.0 // indirect
|
||||
github.com/go-acme/jdcloud-sdk-go v1.64.0 // indirect
|
||||
@@ -279,7 +278,7 @@ require (
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/linode/linodego v1.69.1 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.6.10 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
@@ -391,7 +390,6 @@ require (
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/oauth2 v0.37.0 // indirect
|
||||
golang.org/x/term v0.46.0 // indirect
|
||||
@@ -408,7 +406,6 @@ require (
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260501160325-927ab1f70cd6 // indirect
|
||||
k8s.io/streaming v0.36.3 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.3 // indirect
|
||||
@@ -423,6 +420,10 @@ replace (
|
||||
github.com/gorilla/mux => github.com/containous/mux v0.0.0-20250523120546-41b6ec3aed59
|
||||
)
|
||||
|
||||
// software.sslmate.com serves the repository as static files, and the git shipped with macOS links an old libcurl that mishandles the resulting 404s over HTTP/2, so GOPROXY=direct fetches fail for macOS users.
|
||||
// The SSLMate GitHub mirror carries the same commit and the same go.mod bytes.
|
||||
replace software.sslmate.com/src/go-pkcs12 => github.com/SSLMate/go-pkcs12 v0.7.3
|
||||
|
||||
// ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules
|
||||
// tencentcloud uses monorepo with multimodule but the go.mod files are incomplete.
|
||||
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners:
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
hostname: foo.com
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: GRPCRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
# This route has no host name. The listener gives it the host name foo.com.
|
||||
# Thus this route gives the same rule as the route below.
|
||||
# This route is also the older of the two routes. The creation time makes the decision.
|
||||
---
|
||||
kind: GRPCRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: grpc-app-catch-all
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:01Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
|
||||
---
|
||||
kind: GRPCRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: grpc-app-hostname
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:02Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
hostnames:
|
||||
- foo.com
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners:
|
||||
- name: http
|
||||
protocol: HTTP
|
||||
port: 80
|
||||
hostname: foo.com
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- kind: HTTPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
namespaces:
|
||||
from: Same
|
||||
|
||||
# This route has no host name. The listener gives it the host name foo.com.
|
||||
# Thus this route gives the same rule as the route below.
|
||||
# This route is also the older of the two routes. The creation time makes the decision.
|
||||
---
|
||||
kind: HTTPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: http-app-catch-all
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:01Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
|
||||
---
|
||||
kind: HTTPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: http-app-hostname
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:02Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
hostnames:
|
||||
- "foo.com"
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: whoami
|
||||
port: 80
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-tcp-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners:
|
||||
- name: tcp
|
||||
protocol: TCP
|
||||
port: 9000
|
||||
allowedRoutes:
|
||||
namespaces:
|
||||
from: Same
|
||||
kinds:
|
||||
- kind: TCPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-tls-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners:
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9000
|
||||
tls:
|
||||
mode: Passthrough
|
||||
allowedRoutes:
|
||||
namespaces:
|
||||
from: Same
|
||||
kinds:
|
||||
- kind: TLSRoute
|
||||
group: gateway.networking.k8s.io
|
||||
|
||||
# A TLSRoute without host names and a TCPRoute both give the rule HostSNI("*").
|
||||
# The TLSRoute router is evaluated by the TLS muxer, and the TCPRoute router by the non-TLS muxer.
|
||||
# Thus the two routers do not compete, and Traefik creates both.
|
||||
---
|
||||
kind: TLSRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tls-app
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-tls-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tcp-app
|
||||
namespace: default
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-tcp-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-tcp-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners:
|
||||
- name: tcp
|
||||
protocol: TCP
|
||||
port: 9000
|
||||
allowedRoutes:
|
||||
namespaces:
|
||||
from: Same
|
||||
kinds:
|
||||
- kind: TCPRoute
|
||||
group: gateway.networking.k8s.io
|
||||
|
||||
# A TCPRoute has no routing criteria. Thus the two routes below give the rule HostSNI("*").
|
||||
# The route below is the older of the two routes. The creation time makes the decision.
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tcp-app-first
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:01Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-tcp-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
|
||||
---
|
||||
kind: TCPRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tcp-app-second
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:02Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-tcp-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
kind: GatewayClass
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-gateway-class
|
||||
spec:
|
||||
controllerName: traefik.io/gateway-controller
|
||||
|
||||
---
|
||||
kind: Gateway
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: my-tls-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: my-gateway-class
|
||||
listeners:
|
||||
- name: tls
|
||||
protocol: TLS
|
||||
port: 9000
|
||||
hostname: foo.com
|
||||
tls:
|
||||
mode: Passthrough
|
||||
allowedRoutes:
|
||||
namespaces:
|
||||
from: Same
|
||||
kinds:
|
||||
- kind: TLSRoute
|
||||
group: gateway.networking.k8s.io
|
||||
|
||||
# This route has no host name. The listener gives it the host name foo.com.
|
||||
# Thus this route gives the same rule as the route below.
|
||||
# This route is also the older of the two routes. The creation time makes the decision.
|
||||
---
|
||||
kind: TLSRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tls-app-catch-all
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:01Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-tls-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
|
||||
---
|
||||
kind: TLSRoute
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
metadata:
|
||||
name: tls-app-hostname
|
||||
namespace: default
|
||||
creationTimestamp: "2024-01-01T00:00:02Z"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: my-tls-gateway
|
||||
kind: Gateway
|
||||
group: gateway.networking.k8s.io
|
||||
hostnames:
|
||||
- foo.com
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: whoamitcp
|
||||
port: 9000
|
||||
weight: 1
|
||||
kind: Service
|
||||
group: ""
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func (p *Provider) loadGRPCRoute(ctx context.Context, gateways []gatewayWithListeners, route *gatev1.GRPCRoute, conf *dynamic.Configuration, attachedRoutes attachedRoutes, statusReport *statusReport) {
|
||||
func (p *Provider) loadGRPCRoute(ctx context.Context, gateways []gatewayWithListeners, route *gatev1.GRPCRoute, conf *dynamic.Configuration, attachedRoutes attachedRoutes, servedRules servedRules, statusReport *statusReport) {
|
||||
logger := log.Ctx(ctx).With().
|
||||
Str("grpc_route", route.Name).
|
||||
Str("namespace", route.Namespace).
|
||||
@@ -81,13 +81,21 @@ func (p *Provider) loadGRPCRoute(ctx context.Context, gateways []gatewayWithList
|
||||
|
||||
// The ResolvedRefs condition must be reported for every parentRef,
|
||||
// even when the route does not attach to the listener.
|
||||
routeConf, condition := p.loadGRPCRouteConfiguration(logger.WithContext(ctx), match.GatewayName, match.GatewayNamespace, listener, route, hostnames, statusReport)
|
||||
routerConfs, condition := p.loadGRPCRouteConfiguration(logger.WithContext(ctx), match.GatewayName, match.GatewayNamespace, listener, route, hostnames, statusReport)
|
||||
if resolvedRefCondition == nil || resolvedRefCondition.Status == metav1.ConditionTrue {
|
||||
resolvedRefCondition = new(condition)
|
||||
}
|
||||
|
||||
if accepted && listener.Attached {
|
||||
mergeHTTPConfiguration(routeConf, conf)
|
||||
for _, rc := range routerConfs {
|
||||
router := rc.Conf.HTTP.Routers[rc.Name]
|
||||
if servedBy, alreadyServed := servedRules.register(rc.Name, router.ParentRefs, router.Rule); alreadyServed {
|
||||
logger.Warn().Msgf("Traefik does not create router %q, because router %q serves the rule %q under the same parent routers", rc.Name, servedBy, router.Rule)
|
||||
continue
|
||||
}
|
||||
|
||||
mergeHTTPConfiguration(rc.Conf, conf)
|
||||
}
|
||||
|
||||
// Only consider the route attached if the listener is in an "attached" state.
|
||||
acceptedCondition.Reason = string(gatev1.RouteReasonAccepted)
|
||||
@@ -108,15 +116,14 @@ func (p *Provider) loadGRPCRoute(ctx context.Context, gateways []gatewayWithList
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) loadGRPCRouteConfiguration(ctx context.Context, gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.GRPCRoute, hostnames []gatev1.Hostname, statusReport *statusReport) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
}
|
||||
// grpcRouteRouter is a router of a GRPCRoute, with the configuration of its resources.
|
||||
type grpcRouteRouter struct {
|
||||
Name string
|
||||
Conf *dynamic.Configuration
|
||||
}
|
||||
|
||||
func (p *Provider) loadGRPCRouteConfiguration(ctx context.Context, gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.GRPCRoute, hostnames []gatev1.Hostname, statusReport *statusReport) ([]grpcRouteRouter, metav1.Condition) {
|
||||
var routers []grpcRouteRouter
|
||||
|
||||
condition := metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
@@ -134,7 +141,6 @@ func (p *Provider) loadGRPCRouteConfiguration(ctx context.Context, gatewayName,
|
||||
|
||||
for _, match := range matches {
|
||||
rule, priority := buildGRPCMatchRule(hostnames, match)
|
||||
|
||||
router := dynamic.Router{
|
||||
// "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax.
|
||||
RuleSyntax: "default",
|
||||
@@ -143,15 +149,25 @@ func (p *Provider) loadGRPCRouteConfiguration(ctx context.Context, gatewayName,
|
||||
ParentRefs: listener.RouterNames,
|
||||
}
|
||||
|
||||
var err error
|
||||
routerName := makeRouterName(strings.ToLower(kindGRPCRoute), rule, route.Namespace, route.Name, gatewayNamespace, gatewayName, listener.EPName, ri)
|
||||
router.Middlewares, err = p.loadGRPCMiddlewares(conf, route.Namespace, routerName, routeRule.Filters)
|
||||
|
||||
routerConf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
router.Middlewares, err = p.loadGRPCMiddlewares(routerConf, route.Namespace, routerName, routeRule.Filters)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to load GRPC route filters")
|
||||
|
||||
errWrrName := routerName + "-err-wrr"
|
||||
conf.HTTP.Services[errWrrName] = &dynamic.Service{
|
||||
routerConf.HTTP.Services[errWrrName] = &dynamic.Service{
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
@@ -169,17 +185,18 @@ func (p *Provider) loadGRPCRouteConfiguration(ctx context.Context, gatewayName,
|
||||
|
||||
default:
|
||||
var serviceCondition *metav1.Condition
|
||||
router.Service, serviceCondition = p.loadGRPCService(gatewayName, listener, conf, routerName, routeRule, route, statusReport)
|
||||
router.Service, serviceCondition = p.loadGRPCService(gatewayName, listener, routerConf, routerName, routeRule, route, statusReport)
|
||||
if serviceCondition != nil {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
}
|
||||
|
||||
conf.HTTP.Routers[routerName] = &router
|
||||
routerConf.HTTP.Routers[routerName] = &router
|
||||
routers = append(routers, grpcRouteRouter{Name: routerName, Conf: routerConf})
|
||||
}
|
||||
}
|
||||
|
||||
return conf, condition
|
||||
return routers, condition
|
||||
}
|
||||
|
||||
func (p *Provider) loadGRPCService(gatewayName string, listener gatewayListener, conf *dynamic.Configuration, routerName string, routeRule gatev1.GRPCRouteRule, route *gatev1.GRPCRoute, statusReport *statusReport) (string, *metav1.Condition) {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func (p *Provider) loadHTTPRoute(ctx context.Context, gateways []gatewayWithListeners, route *gatev1.HTTPRoute, conf *dynamic.Configuration, attachedRoutes attachedRoutes, statusReport *statusReport) {
|
||||
func (p *Provider) loadHTTPRoute(ctx context.Context, gateways []gatewayWithListeners, route *gatev1.HTTPRoute, conf *dynamic.Configuration, attachedRoutes attachedRoutes, servedRules servedRules, statusReport *statusReport) {
|
||||
logger := log.Ctx(ctx).With().
|
||||
Str("http_route", route.Name).
|
||||
Str("namespace", route.Namespace).
|
||||
@@ -85,13 +85,21 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, gateways []gatewayWithList
|
||||
|
||||
// The ResolvedRefs condition must be reported for every parentRef,
|
||||
// even when the route does not attach to the listener.
|
||||
routeConf, condition := p.loadHTTPRouteConfiguration(logger.WithContext(ctx), match.GatewayName, match.GatewayNamespace, listener, route, hostnames, statusReport)
|
||||
routerConfs, condition := p.loadHTTPRouteConfiguration(logger.WithContext(ctx), match.GatewayName, match.GatewayNamespace, listener, route, hostnames, statusReport)
|
||||
if resolvedRefCondition == nil || resolvedRefCondition.Status == metav1.ConditionTrue {
|
||||
resolvedRefCondition = new(condition)
|
||||
}
|
||||
|
||||
if accepted && listener.Attached {
|
||||
mergeHTTPConfiguration(routeConf, conf)
|
||||
for _, rc := range routerConfs {
|
||||
router := rc.Conf.HTTP.Routers[rc.Name]
|
||||
if servedBy, alreadyServed := servedRules.register(rc.Name, router.ParentRefs, router.Rule); alreadyServed {
|
||||
logger.Warn().Msgf("Traefik does not create router %q, because router %q serves the rule %q under the same parent routers", rc.Name, servedBy, router.Rule)
|
||||
continue
|
||||
}
|
||||
|
||||
mergeHTTPConfiguration(rc.Conf, conf)
|
||||
}
|
||||
|
||||
// Only consider the route attached if the listener is in an "attached" state.
|
||||
acceptedCondition.Reason = string(gatev1.RouteReasonAccepted)
|
||||
@@ -112,15 +120,14 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, gateways []gatewayWithList
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname, statusReport *statusReport) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
}
|
||||
// httpRouteRouter is a router of an HTTPRoute, with the configuration of its resources.
|
||||
type httpRouteRouter struct {
|
||||
Name string
|
||||
Conf *dynamic.Configuration
|
||||
}
|
||||
|
||||
func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.HTTPRoute, hostnames []gatev1.Hostname, statusReport *statusReport) ([]httpRouteRouter, metav1.Condition) {
|
||||
var routers []httpRouteRouter
|
||||
|
||||
condition := metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
@@ -141,16 +148,26 @@ func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName,
|
||||
ParentRefs: listener.RouterNames,
|
||||
}
|
||||
|
||||
var err error
|
||||
routerName := makeRouterName(strings.ToLower(kindHTTPRoute), rule, route.Namespace, route.Name, gatewayNamespace, gatewayName, listener.EPName, ri)
|
||||
|
||||
routerConf := &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
ServersTransports: make(map[string]*dynamic.ServersTransport),
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
// TODO loadMiddlewares errors could change the condition.
|
||||
router.Middlewares, err = p.loadMiddlewares(conf, route.Namespace, routerName, routeRule.Filters, match.Path)
|
||||
router.Middlewares, err = p.loadMiddlewares(routerConf, route.Namespace, routerName, routeRule.Filters, match.Path)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Unable to load HTTPRoute filters")
|
||||
|
||||
errWrrName := routerName + "-err-wrr"
|
||||
conf.HTTP.Services[errWrrName] = &dynamic.Service{
|
||||
routerConf.HTTP.Services[errWrrName] = &dynamic.Service{
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
@@ -179,7 +196,7 @@ func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName,
|
||||
|
||||
default:
|
||||
var serviceCondition *metav1.Condition
|
||||
router.Service, serviceCondition = p.loadWRRService(ctx, gatewayName, listener, conf, routerName, routeRule, route, match.Path, statusReport)
|
||||
router.Service, serviceCondition = p.loadWRRService(ctx, gatewayName, listener, routerConf, routerName, routeRule, route, match.Path, statusReport)
|
||||
if serviceCondition != nil {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
@@ -187,11 +204,12 @@ func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName,
|
||||
|
||||
p.applyRouterTransform(ctx, &router, route)
|
||||
|
||||
conf.HTTP.Routers[routerName] = &router
|
||||
routerConf.HTTP.Routers[routerName] = &router
|
||||
routers = append(routers, httpRouteRouter{Name: routerName, Conf: routerConf})
|
||||
}
|
||||
}
|
||||
|
||||
return conf, condition
|
||||
return routers, condition
|
||||
}
|
||||
|
||||
func (p *Provider) loadWRRService(ctx context.Context, gatewayName string, listener gatewayListener, conf *dynamic.Configuration, routerName string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute, pathMatch *gatev1.HTTPPathMatch, statusReport *statusReport) (string, *metav1.Condition) {
|
||||
|
||||
@@ -506,19 +506,16 @@ func (p *Provider) loadHTTPAndGRPCRoutes(ctx context.Context, gateways []gateway
|
||||
routes = append(routes, route)
|
||||
}
|
||||
|
||||
slices.SortStableFunc(routes, func(a, b metav1.Object) int {
|
||||
return cmp.Or(a.GetCreationTimestamp().Time.Compare(b.GetCreationTimestamp().Time),
|
||||
strings.Compare(a.GetNamespace(), b.GetNamespace()),
|
||||
strings.Compare(a.GetName(), b.GetName()))
|
||||
})
|
||||
slices.SortStableFunc(routes, compareRoutes)
|
||||
|
||||
attached := make(attachedRoutes)
|
||||
served := make(servedRules)
|
||||
for _, route := range routes {
|
||||
switch route := route.(type) {
|
||||
case *gatev1.HTTPRoute:
|
||||
p.loadHTTPRoute(ctx, gateways, route, conf, attached, statusReport)
|
||||
p.loadHTTPRoute(ctx, gateways, route, conf, attached, served, statusReport)
|
||||
case *gatev1.GRPCRoute:
|
||||
p.loadGRPCRoute(ctx, gateways, route, conf, attached, statusReport)
|
||||
p.loadGRPCRoute(ctx, gateways, route, conf, attached, served, statusReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1379,6 +1376,41 @@ func allowRoute(listener gatewayListener, routeNamespace, routeKind string) bool
|
||||
})
|
||||
}
|
||||
|
||||
// compareRoutes orders the routes as the specification asks for the ones matching
|
||||
// a request equally well to be discriminated.
|
||||
func compareRoutes(a, b metav1.Object) int {
|
||||
return cmp.Or(a.GetCreationTimestamp().Time.Compare(b.GetCreationTimestamp().Time),
|
||||
strings.Compare(a.GetNamespace(), b.GetNamespace()),
|
||||
strings.Compare(a.GetName(), b.GetName()))
|
||||
}
|
||||
|
||||
// servedRuleKey identifies a rule within the scope its router competes in.
|
||||
// A TCP router competes in the muxer of its entry points,
|
||||
// and an HTTP router in the muxer of its listener parent routers, as it has no entry points of its own.
|
||||
type servedRuleKey struct {
|
||||
Scope string
|
||||
Rule string
|
||||
}
|
||||
|
||||
// servedRules keeps the router that serves each rule.
|
||||
// Two routers that compete and have the same rule match the same requests.
|
||||
// Thus only the first router that a muxer evaluates can serve a request.
|
||||
type servedRules map[servedRuleKey]string
|
||||
|
||||
// register keeps the given router as the router that serves its rule within the given scope.
|
||||
// If a different router serves that rule already, register gives its name, and true.
|
||||
func (sr servedRules) register(name string, scope []string, rule string) (string, bool) {
|
||||
key := servedRuleKey{Scope: strings.Join(scope, ","), Rule: rule}
|
||||
|
||||
if servedBy, served := sr[key]; served {
|
||||
return servedBy, true
|
||||
}
|
||||
|
||||
sr[key] = name
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// listenerRef identifies a listener of a Gateway.
|
||||
type listenerRef struct {
|
||||
Name string
|
||||
|
||||
@@ -3705,6 +3705,74 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTPRoutes with identical rules",
|
||||
paths: []string{"services.yml", "httproute/with_identical_rules.yml"},
|
||||
entryPoints: map[string]Entrypoint{"web": {
|
||||
Address: ":80",
|
||||
}},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"listener-web-http-foo-com-5f20d578e2e3eccac466": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: `Host("foo.com")`,
|
||||
},
|
||||
"httproute-default-http-app-catch-all-gw-default-my-gateway-ep-web-0-402d8b0319543c8e637b": {
|
||||
Service: "httproute-default-http-app-catch-all-gw-default-my-gateway-ep-web-0-402d8b0319543c8e637b-wrr",
|
||||
Rule: `Host("foo.com") && PathPrefix("/")`,
|
||||
Priority: 9,
|
||||
RuleSyntax: "default",
|
||||
ParentRefs: []string{"listener-web-http-foo-com-5f20d578e2e3eccac466"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"httproute-default-http-app-catch-all-gw-default-my-gateway-ep-web-0-402d8b0319543c8e637b-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "httproute-default-http-app-catch-all-gw-default-my-gateway-ep-web-0-402d8b0319543c8e637b-svc-default-whoami-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"httproute-default-http-app-catch-all-gw-default-my-gateway-ep-web-0-402d8b0319543c8e637b-svc-default-whoami-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: new(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -4963,6 +5031,74 @@ func TestLoadGRPCRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "GRPCRoutes with identical rules",
|
||||
paths: []string{"services.yml", "grpcroute/with_identical_rules.yml"},
|
||||
entryPoints: map[string]Entrypoint{"web": {
|
||||
Address: ":80",
|
||||
}},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"listener-web-http-foo-com-5f20d578e2e3eccac466": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: `Host("foo.com")`,
|
||||
},
|
||||
"grpcroute-default-grpc-app-catch-all-gw-default-my-gateway-ep-web-0-24ad420c9f1560b62d23": {
|
||||
Service: "grpcroute-default-grpc-app-catch-all-gw-default-my-gateway-ep-web-0-24ad420c9f1560b62d23-wrr",
|
||||
Rule: `Host("foo.com") && PathPrefix("/")`,
|
||||
Priority: 22,
|
||||
RuleSyntax: "default",
|
||||
ParentRefs: []string{"listener-web-http-foo-com-5f20d578e2e3eccac466"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"grpcroute-default-grpc-app-catch-all-gw-default-my-gateway-ep-web-0-24ad420c9f1560b62d23-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "grpcroute-default-grpc-app-catch-all-gw-default-my-gateway-ep-web-0-24ad420c9f1560b62d23-svc-default-whoami-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"grpcroute-default-grpc-app-catch-all-gw-default-my-gateway-ep-web-0-24ad420c9f1560b62d23-svc-default-whoami-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "h2c://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "h2c://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: new(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -5707,12 +5843,6 @@ func TestLoadTCPRoutes(t *testing.T) {
|
||||
Rule: `HostSNI("*")`,
|
||||
RuleSyntax: "default",
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-1-1-2303f3a24dbdd3670244": {
|
||||
EntryPoints: []string{"tcp-1"},
|
||||
Service: "tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-1-1-2303f3a24dbdd3670244-wrr",
|
||||
Rule: `HostSNI("*")`,
|
||||
RuleSyntax: "default",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
@@ -5726,16 +5856,6 @@ func TestLoadTCPRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-1-1-2303f3a24dbdd3670244-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-1-1-2303f3a24dbdd3670244-svc-default-whoamitcp-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-1-0-1767e543195002a45b0b-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
@@ -5748,18 +5868,6 @@ func TestLoadTCPRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-1-1-2303f3a24dbdd3670244-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.9:10000",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.10:10000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
@@ -6364,6 +6472,64 @@ func TestLoadTCPRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCPRoutes with identical rules",
|
||||
paths: []string{"services.yml", "tcproute/with_identical_rules.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tcp": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"tcproute-default-tcp-app-first-gw-default-my-tcp-gateway-ep-tcp-0-79a3b41b9e16bdaa2841": {
|
||||
EntryPoints: []string{"tcp"},
|
||||
Service: "tcproute-default-tcp-app-first-gw-default-my-tcp-gateway-ep-tcp-0-79a3b41b9e16bdaa2841-wrr",
|
||||
Rule: `HostSNI("*")`,
|
||||
RuleSyntax: "default",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"tcproute-default-tcp-app-first-gw-default-my-tcp-gateway-ep-tcp-0-79a3b41b9e16bdaa2841-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "tcproute-default-tcp-app-first-gw-default-my-tcp-gateway-ep-tcp-0-79a3b41b9e16bdaa2841-svc-default-whoamitcp-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-first-gw-default-my-tcp-gateway-ep-tcp-0-79a3b41b9e16bdaa2841-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.9:9000",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.10:9000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -7489,16 +7655,6 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||
Passthrough: true,
|
||||
},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-gateway-ep-tcp-1-1-c675f072c5cdb7649f23": {
|
||||
EntryPoints: []string{"tcp-1"},
|
||||
Service: "tlsroute-default-tls-app-gw-default-my-gateway-ep-tcp-1-1-c675f072c5cdb7649f23-wrr",
|
||||
Priority: 0,
|
||||
Rule: `HostSNI("*")`,
|
||||
RuleSyntax: "default",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
@@ -7515,16 +7671,6 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-gateway-ep-tcp-1-1-c675f072c5cdb7649f23-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "tlsroute-default-tls-app-gw-default-my-gateway-ep-tcp-1-1-c675f072c5cdb7649f23-svc-default-whoamitcp-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-gateway-ep-tcp-1-0-50b1500bd2ad9fd2008a-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
@@ -7537,18 +7683,6 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-gateway-ep-tcp-1-1-c675f072c5cdb7649f23-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.9:10000",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.10:10000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
@@ -8087,6 +8221,77 @@ func TestLoadTLSRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLSRoutes with identical rules",
|
||||
paths: []string{"services.yml", "tlsroute/with_identical_rules.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tcp": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"tlsroute-default-tls-app-catch-all-gw-default-my-tls-gateway-ep-tcp-0-86149371d80f428d4c2d": {
|
||||
EntryPoints: []string{"tcp"},
|
||||
Service: "tlsroute-default-tls-app-catch-all-gw-default-my-tls-gateway-ep-tcp-0-86149371d80f428d4c2d-wrr",
|
||||
Rule: `HostSNI("foo.com")`,
|
||||
Priority: 7,
|
||||
RuleSyntax: "default",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
},
|
||||
"deny-unknown-host": {
|
||||
Rule: "HostSNI(`*`) && !ALPN(`h2`) && !ALPN(`http/1.1`)",
|
||||
Priority: 1,
|
||||
Service: "deny-unknown-host",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"tlsroute-default-tls-app-catch-all-gw-default-my-tls-gateway-ep-tcp-0-86149371d80f428d4c2d-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "tlsroute-default-tls-app-catch-all-gw-default-my-tls-gateway-ep-tcp-0-86149371d80f428d4c2d-svc-default-whoamitcp-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tlsroute-default-tls-app-catch-all-gw-default-my-tls-gateway-ep-tcp-0-86149371d80f428d4c2d-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.9:9000",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.10:9000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"deny-unknown-host": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@@ -8765,20 +8970,6 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||
Options: "listener-websecure-https-439feda8e3f1dbdca529",
|
||||
},
|
||||
},
|
||||
"httproute-default-http-app-default-gw-default-my-gateway-ep-web-0-3b55179f1d48675f1432": {
|
||||
Service: "httproute-default-http-app-default-gw-default-my-gateway-ep-web-0-3b55179f1d48675f1432-wrr",
|
||||
Rule: `PathPrefix("/")`,
|
||||
Priority: 2,
|
||||
RuleSyntax: "default",
|
||||
ParentRefs: []string{"listener-web-http-5b3226d4ebd42b7200f1"},
|
||||
},
|
||||
"httproute-default-http-app-default-gw-default-my-gateway-ep-websecure-0-4a26538fbaaf60dc52c9": {
|
||||
Service: "httproute-default-http-app-default-gw-default-my-gateway-ep-websecure-0-4a26538fbaaf60dc52c9-wrr",
|
||||
Rule: `PathPrefix("/")`,
|
||||
Priority: 2,
|
||||
RuleSyntax: "default",
|
||||
ParentRefs: []string{"listener-websecure-https-439feda8e3f1dbdca529"},
|
||||
},
|
||||
"httproute-bar-http-app-bar-gw-default-my-gateway-ep-web-0-cf99cd1374e6056a6755": {
|
||||
Service: "httproute-bar-http-app-bar-gw-default-my-gateway-ep-web-0-cf99cd1374e6056a6755-wrr",
|
||||
Rule: `PathPrefix("/")`,
|
||||
@@ -8796,60 +8987,6 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"httproute-default-http-app-default-gw-default-my-gateway-ep-web-0-3b55179f1d48675f1432-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "httproute-default-http-app-default-gw-default-my-gateway-ep-web-0-3b55179f1d48675f1432-svc-default-whoami-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"httproute-default-http-app-default-gw-default-my-gateway-ep-websecure-0-4a26538fbaaf60dc52c9-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "httproute-default-http-app-default-gw-default-my-gateway-ep-websecure-0-4a26538fbaaf60dc52c9-svc-default-whoami-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"httproute-default-http-app-default-gw-default-my-gateway-ep-web-0-3b55179f1d48675f1432-svc-default-whoami-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: new(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"httproute-default-http-app-default-gw-default-my-gateway-ep-websecure-0-4a26538fbaaf60dc52c9-svc-default-whoami-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: new(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"httproute-bar-http-app-bar-gw-default-my-gateway-ep-web-0-cf99cd1374e6056a6755-svc-bar-whoami-bar-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
@@ -9396,6 +9533,96 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLSRoute and TCPRoute with the same rule on the same entry point",
|
||||
paths: []string{"services.yml", "mixed/with_tlsroute_and_tcproute_on_same_entrypoint.yml"},
|
||||
entryPoints: map[string]Entrypoint{
|
||||
"tcp": {Address: ":9000"},
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"deny-unknown-host": {
|
||||
Rule: "HostSNI(`*`) && !ALPN(`h2`) && !ALPN(`http/1.1`)",
|
||||
Priority: 1,
|
||||
Service: "deny-unknown-host",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-tls-gateway-ep-tcp-0-b5a69536e2d72608adeb": {
|
||||
EntryPoints: []string{"tcp"},
|
||||
Service: "tlsroute-default-tls-app-gw-default-my-tls-gateway-ep-tcp-0-b5a69536e2d72608adeb-wrr",
|
||||
Rule: `HostSNI("*")`,
|
||||
RuleSyntax: "default",
|
||||
TLS: &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: true,
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-0-ada211f39fad8759e161": {
|
||||
EntryPoints: []string{"tcp"},
|
||||
Service: "tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-0-ada211f39fad8759e161-wrr",
|
||||
Rule: `HostSNI("*")`,
|
||||
RuleSyntax: "default",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"deny-unknown-host": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-tls-gateway-ep-tcp-0-b5a69536e2d72608adeb-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "tlsroute-default-tls-app-gw-default-my-tls-gateway-ep-tcp-0-b5a69536e2d72608adeb-svc-default-whoamitcp-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tlsroute-default-tls-app-gw-default-my-tls-gateway-ep-tcp-0-b5a69536e2d72608adeb-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{Address: "10.10.0.9:9000"},
|
||||
{Address: "10.10.0.10:9000"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-0-ada211f39fad8759e161-wrr": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-0-ada211f39fad8759e161-svc-default-whoamitcp-0",
|
||||
Weight: new(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"tcproute-default-tcp-app-gw-default-my-tcp-gateway-ep-tcp-0-ada211f39fad8759e161-svc-default-whoamitcp-0": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{Address: "10.10.0.9:9000"},
|
||||
{Address: "10.10.0.10:9000"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
@@ -31,18 +30,16 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
// TCPRoutes attaching to the same listener all match every connection,
|
||||
// and the specification gives precedence to the oldest one.
|
||||
// https://gateway-api.sigs.k8s.io/guides/api-design/#conflicts
|
||||
slices.SortStableFunc(routes, func(a, b *gatev1.TCPRoute) int {
|
||||
if cmpTime := a.CreationTimestamp.Time.Compare(b.CreationTimestamp.Time); cmpTime != 0 {
|
||||
return cmpTime
|
||||
}
|
||||
slices.SortStableFunc(routes, func(a, b *gatev1.TCPRoute) int { return compareRoutes(a, b) })
|
||||
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Namespace, b.Namespace),
|
||||
cmp.Compare(a.Name, b.Name),
|
||||
)
|
||||
})
|
||||
served := make(servedRules)
|
||||
|
||||
for _, route := range routes {
|
||||
logger := log.Ctx(ctx).With().
|
||||
Str("tcp_route", route.Name).
|
||||
Str("namespace", route.Namespace).
|
||||
Logger()
|
||||
|
||||
routeParentRefs := matchingGatewayListenersForParentRef(gateways, route.Namespace, route.Spec.ParentRefs)
|
||||
if len(routeParentRefs) == 0 {
|
||||
continue
|
||||
@@ -75,7 +72,7 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
|
||||
// The ResolvedRefs condition must be reported for every parentRef,
|
||||
// even when the route does not attach to the listener.
|
||||
routeConf, condition := p.loadTCPRoute(match.GatewayName, match.GatewayNamespace, listener, route)
|
||||
routerConfs, condition := p.loadTCPRoute(match.GatewayName, match.GatewayNamespace, listener, route)
|
||||
if resolvedRefCondition == nil || resolvedRefCondition.Status == metav1.ConditionTrue {
|
||||
resolvedRefCondition = new(condition)
|
||||
}
|
||||
@@ -86,7 +83,15 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
// A TCP listener supports no route kind other than TCPRoute, so the
|
||||
// first route to attach to it is the one to bind.
|
||||
if listener.Status.AttachedRoutes == 1 {
|
||||
mergeTCPConfiguration(routeConf, conf)
|
||||
for _, rc := range routerConfs {
|
||||
router := rc.Conf.TCP.Routers[rc.Name]
|
||||
if servedBy, alreadyServed := served.register(rc.Name, router.EntryPoints, router.Rule); alreadyServed {
|
||||
logger.Warn().Msgf("Traefik does not create router %q, because router %q serves the rule %q on the same entry points", rc.Name, servedBy, router.Rule)
|
||||
continue
|
||||
}
|
||||
|
||||
mergeTCPConfiguration(rc.Conf, conf)
|
||||
}
|
||||
}
|
||||
|
||||
// Only consider the route attached if the listener is in an "attached" state.
|
||||
@@ -109,15 +114,14 @@ func (p *Provider) loadTCPRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) loadTCPRoute(gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.TCPRoute) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
}
|
||||
// tcpRouteRouter is a router of a TCPRoute, with the configuration of its resources.
|
||||
type tcpRouteRouter struct {
|
||||
Name string
|
||||
Conf *dynamic.Configuration
|
||||
}
|
||||
|
||||
func (p *Provider) loadTCPRoute(gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.TCPRoute) ([]tcpRouteRouter, metav1.Condition) {
|
||||
var routers []tcpRouteRouter
|
||||
|
||||
condition := metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
@@ -149,6 +153,15 @@ func (p *Provider) loadTCPRoute(gatewayName, gatewayNamespace string, listener g
|
||||
// Routing criteria should be introduced at some point.
|
||||
routerName := makeRouterName(strings.ToLower(kindTCPRoute), "", route.Namespace, route.Name, gatewayNamespace, gatewayName, listener.EPName, ri)
|
||||
|
||||
routerConf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
}
|
||||
|
||||
if len(rule.BackendRefs) == 1 && isInternalService(rule.BackendRefs[0]) {
|
||||
if !isCrossProviderNamespaceAllowed(p.CrossProviderNamespaces, route.Namespace) {
|
||||
condition = metav1.Condition{
|
||||
@@ -164,20 +177,22 @@ func (p *Provider) loadTCPRoute(gatewayName, gatewayNamespace string, listener g
|
||||
}
|
||||
|
||||
router.Service = string(rule.BackendRefs[0].Name)
|
||||
conf.TCP.Routers[routerName] = &router
|
||||
routerConf.TCP.Routers[routerName] = &router
|
||||
routers = append(routers, tcpRouteRouter{Name: routerName, Conf: routerConf})
|
||||
continue
|
||||
}
|
||||
|
||||
var serviceCondition *metav1.Condition
|
||||
router.Service, serviceCondition = p.loadTCPWRRService(conf, routerName, rule.BackendRefs, route)
|
||||
router.Service, serviceCondition = p.loadTCPWRRService(routerConf, routerName, rule.BackendRefs, route)
|
||||
if serviceCondition != nil {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
|
||||
conf.TCP.Routers[routerName] = &router
|
||||
routerConf.TCP.Routers[routerName] = &router
|
||||
routers = append(routers, tcpRouteRouter{Name: routerName, Conf: routerConf})
|
||||
}
|
||||
|
||||
return conf, condition
|
||||
return routers, condition
|
||||
}
|
||||
|
||||
// loadTCPWRRService is generating a WRR service, even when there is only one target.
|
||||
|
||||
@@ -29,7 +29,17 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
return
|
||||
}
|
||||
|
||||
// The provider loads the routes in the order that the specification gives,
|
||||
// to make a decision between the routes that match a connection equally well.
|
||||
slices.SortStableFunc(routes, func(a, b *gatev1.TLSRoute) int { return compareRoutes(a, b) })
|
||||
|
||||
served := make(servedRules)
|
||||
|
||||
for _, route := range routes {
|
||||
logger := log.Ctx(ctx).With().
|
||||
Str("tls_route", route.Name).
|
||||
Str("namespace", route.Namespace).Logger()
|
||||
|
||||
routeParentRefs := matchingGatewayListenersForParentRef(gateways, route.Namespace, route.Spec.ParentRefs)
|
||||
if len(routeParentRefs) == 0 {
|
||||
continue
|
||||
@@ -70,13 +80,21 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
|
||||
// The ResolvedRefs condition must be reported for every parentRef,
|
||||
// even when the route does not attach to the listener.
|
||||
routeConf, condition := p.loadTLSRoute(match.GatewayName, match.GatewayNamespace, listener, route, hostnames, statusReport)
|
||||
routerConfs, condition := p.loadTLSRoute(match.GatewayName, match.GatewayNamespace, listener, route, hostnames, statusReport)
|
||||
if resolvedRefCondition == nil || resolvedRefCondition.Status == metav1.ConditionTrue {
|
||||
resolvedRefCondition = new(condition)
|
||||
}
|
||||
|
||||
if accepted && listener.Attached {
|
||||
mergeTCPConfiguration(routeConf, conf)
|
||||
for _, rc := range routerConfs {
|
||||
router := rc.Conf.TCP.Routers[rc.Name]
|
||||
if servedBy, alreadyServed := served.register(rc.Name, router.EntryPoints, router.Rule); alreadyServed {
|
||||
logger.Warn().Msgf("Traefik does not create router %q, because router %q serves the rule %q on the same entry points", rc.Name, servedBy, router.Rule)
|
||||
continue
|
||||
}
|
||||
|
||||
mergeTCPConfiguration(rc.Conf, conf)
|
||||
}
|
||||
|
||||
// Only consider the route attached if the listener is in an "attached" state.
|
||||
acceptedCondition.Reason = string(gatev1.RouteReasonAccepted)
|
||||
@@ -112,15 +130,14 @@ func (p *Provider) loadTLSRoutes(ctx context.Context, gateways []gatewayWithList
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) loadTLSRoute(gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.TLSRoute, hostnames []gatev1.Hostname, statusReport *statusReport) (*dynamic.Configuration, metav1.Condition) {
|
||||
conf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
}
|
||||
// tlsRouteRouter is a router of a TLSRoute, with the configuration of its resources.
|
||||
type tlsRouteRouter struct {
|
||||
Name string
|
||||
Conf *dynamic.Configuration
|
||||
}
|
||||
|
||||
func (p *Provider) loadTLSRoute(gatewayName, gatewayNamespace string, listener gatewayListener, route *gatev1.TLSRoute, hostnames []gatev1.Hostname, statusReport *statusReport) ([]tlsRouteRouter, metav1.Condition) {
|
||||
var routers []tlsRouteRouter
|
||||
|
||||
condition := metav1.Condition{
|
||||
Type: string(gatev1.RouteConditionResolvedRefs),
|
||||
@@ -151,6 +168,15 @@ func (p *Provider) loadTLSRoute(gatewayName, gatewayNamespace string, listener g
|
||||
// Routing criteria should be introduced at some point.
|
||||
routerName := makeRouterName(strings.ToLower(kindTLSRoute), "", route.Namespace, route.Name, gatewayNamespace, gatewayName, listener.EPName, ri)
|
||||
|
||||
routerConf := &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: make(map[string]*dynamic.TCPRouter),
|
||||
Middlewares: make(map[string]*dynamic.TCPMiddleware),
|
||||
Services: make(map[string]*dynamic.TCPService),
|
||||
ServersTransports: make(map[string]*dynamic.TCPServersTransport),
|
||||
},
|
||||
}
|
||||
|
||||
if len(routeRule.BackendRefs) == 1 && isInternalService(routeRule.BackendRefs[0]) {
|
||||
if !isCrossProviderNamespaceAllowed(p.CrossProviderNamespaces, route.Namespace) {
|
||||
condition = metav1.Condition{
|
||||
@@ -166,20 +192,22 @@ func (p *Provider) loadTLSRoute(gatewayName, gatewayNamespace string, listener g
|
||||
}
|
||||
|
||||
router.Service = string(routeRule.BackendRefs[0].Name)
|
||||
conf.TCP.Routers[routerName] = &router
|
||||
routerConf.TCP.Routers[routerName] = &router
|
||||
routers = append(routers, tlsRouteRouter{Name: routerName, Conf: routerConf})
|
||||
continue
|
||||
}
|
||||
|
||||
var serviceCondition *metav1.Condition
|
||||
router.Service, serviceCondition = p.loadTLSWRRService(gatewayName, listener, conf, routerName, routeRule.BackendRefs, route, statusReport)
|
||||
router.Service, serviceCondition = p.loadTLSWRRService(gatewayName, listener, routerConf, routerName, routeRule.BackendRefs, route, statusReport)
|
||||
if serviceCondition != nil {
|
||||
condition = *serviceCondition
|
||||
}
|
||||
|
||||
conf.TCP.Routers[routerName] = &router
|
||||
routerConf.TCP.Routers[routerName] = &router
|
||||
routers = append(routers, tlsRouteRouter{Name: routerName, Conf: routerConf})
|
||||
}
|
||||
|
||||
return conf, condition
|
||||
return routers, condition
|
||||
}
|
||||
|
||||
// loadTLSWRRService is generating a WRR service, even when there is only one target.
|
||||
|
||||
@@ -875,7 +875,7 @@ func isAliasingHeaderName(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// removeHeadersWithUnderscores removes any request header and trailer whose name contains an underscore character.
|
||||
// removeHeadersWithUnderscores removes any request header whose name contains an underscore character.
|
||||
func removeHeadersWithUnderscores(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for key := range req.Header {
|
||||
@@ -888,7 +888,7 @@ func removeHeadersWithUnderscores(h http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// rejectHeadersWithUnderscores rejects with a 400 Bad Request any request carrying a header or trailer whose name contains an underscore character.
|
||||
// rejectHeadersWithUnderscores rejects with a 400 Bad Request any request carrying a header whose name contains an underscore character.
|
||||
func rejectHeadersWithUnderscores(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for key := range req.Header {
|
||||
@@ -902,7 +902,7 @@ func rejectHeadersWithUnderscores(h http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// removeAliasingHeaders removes any request header and trailer whose name contains a character
|
||||
// removeAliasingHeaders removes any request header whose name contains a character
|
||||
// which is neither a letter, a digit, nor a dash, as such a name aliases another header name.
|
||||
func removeAliasingHeaders(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
@@ -916,7 +916,7 @@ func removeAliasingHeaders(h http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// rejectAliasingHeaders rejects with a 400 Bad Request any request carrying a header or trailer whose name
|
||||
// rejectAliasingHeaders rejects with a 400 Bad Request any request carrying a header whose name
|
||||
// contains a character which is neither a letter, a digit, nor a dash, as such a name aliases another header name.
|
||||
func rejectAliasingHeaders(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
@@ -214,15 +214,15 @@ func (b *Balancer) nextServer() (*namedHandler, error) {
|
||||
if len(healthy) == 1 {
|
||||
return healthy[0], nil
|
||||
}
|
||||
// In order to not get the same backend twice, we make the second call to s.rand.Intn one fewer
|
||||
// In order to not get the same backend twice, we make the second call to b.rand.Intn one fewer
|
||||
// than the length of the slice. We then have to shift over the second index if it is equal or
|
||||
// greater than the first index, wrapping round if needed.
|
||||
// greater than the first index.
|
||||
b.randMu.Lock()
|
||||
n1, n2 := b.rand.Intn(len(healthy)), b.rand.Intn(len(healthy))
|
||||
n1, n2 := b.rand.Intn(len(healthy)), b.rand.Intn(len(healthy)-1)
|
||||
b.randMu.Unlock()
|
||||
|
||||
if n2 == n1 {
|
||||
n2 = (n2 + 1) % len(healthy)
|
||||
if n2 >= n1 {
|
||||
n2++
|
||||
}
|
||||
|
||||
h1, h2 := healthy[n1], healthy[n2]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package p2c
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
@@ -64,6 +65,29 @@ func TestP2C(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestP2CSecondChoiceDistribution makes sure that the second candidate is drawn uniformly among
|
||||
// the other servers, and does not favor the one following the first candidate.
|
||||
func TestP2CSecondChoiceDistribution(t *testing.T) {
|
||||
balancer := New(nil, false)
|
||||
balancer.rand = rand.New(rand.NewSource(12345))
|
||||
|
||||
for _, h := range testHandlers(1, 0, 0) {
|
||||
balancer.handlers = append(balancer.handlers, h)
|
||||
balancer.status[h.name] = struct{}{}
|
||||
}
|
||||
|
||||
selected := map[string]int{}
|
||||
for range 3000 {
|
||||
got, err := balancer.nextServer()
|
||||
require.NoError(t, err)
|
||||
|
||||
selected[got.name]++
|
||||
}
|
||||
|
||||
assert.InDelta(t, 1500, selected["1"], 100)
|
||||
assert.InDelta(t, 1500, selected["2"], 100)
|
||||
}
|
||||
|
||||
func TestSticky(t *testing.T) {
|
||||
balancer := New(&dynamic.Sticky{
|
||||
Cookie: &dynamic.Cookie{
|
||||
|
||||
Reference in new issue
Block a user