Support HTTPRoute retry

This commit is contained in:
Andreï Basdereff authored and GitHub committed 2026-10-27 12:00:08 +01:00
1 parent b46d3ea648
commit 855f81ab09
12 files changed
+441 -22

No files matched your search

@@ -385,6 +385,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| <a id="opt-providers-kubernetesgateway-namespaces" href="#opt-providers-kubernetesgateway-namespaces" title="#opt-providers-kubernetesgateway-namespaces">providers.kubernetesgateway.namespaces</a> | Kubernetes namespaces. | |
| <a id="opt-providers-kubernetesgateway-nativelbbydefault" href="#opt-providers-kubernetesgateway-nativelbbydefault" title="#opt-providers-kubernetesgateway-nativelbbydefault">providers.kubernetesgateway.nativelbbydefault</a> | Defines whether to use Native Kubernetes load-balancing by default. | false |
| <a id="opt-providers-kubernetesgateway-qps" href="#opt-providers-kubernetesgateway-qps" title="#opt-providers-kubernetesgateway-qps">providers.kubernetesgateway.qps</a> | Defines the maximum QPS to the Kubernetes API server. Setting this to a negative value will disable client-side ratelimiting. | 50 |
| <a id="opt-providers-kubernetesgateway-retrymaxrequestbodybytes" href="#opt-providers-kubernetesgateway-retrymaxrequestbodybytes" title="#opt-providers-kubernetesgateway-retrymaxrequestbodybytes">providers.kubernetesgateway.retrymaxrequestbodybytes</a> | Defines the maximum request body size (in bytes) buffered for HTTPRoute retries. If negative, the limit is disabled. | 2097152 |
| <a id="opt-providers-kubernetesgateway-statusaddress-hostname" href="#opt-providers-kubernetesgateway-statusaddress-hostname" title="#opt-providers-kubernetesgateway-statusaddress-hostname">providers.kubernetesgateway.statusaddress.hostname</a> | Hostname used for Kubernetes Gateway status address. | |
| <a id="opt-providers-kubernetesgateway-statusaddress-ip" href="#opt-providers-kubernetesgateway-statusaddress-ip" title="#opt-providers-kubernetesgateway-statusaddress-ip">providers.kubernetesgateway.statusaddress.ip</a> | IP used to set Kubernetes Gateway status address. | |
| <a id="opt-providers-kubernetesgateway-statusaddress-service" href="#opt-providers-kubernetesgateway-statusaddress-service" title="#opt-providers-kubernetesgateway-statusaddress-service">providers.kubernetesgateway.statusaddress.service</a> | Published Kubernetes Service to copy status addresses from. | |
@@ -89,6 +89,7 @@ providers:
| <a id="opt-providers-kubernetesGateway-gateways0-namespace" href="#opt-providers-kubernetesGateway-gateways0-namespace" title="#opt-providers-kubernetesGateway-gateways0-namespace">`providers.kubernetesGateway.gateways[0].namespace`</a> | Gateway namespace. | "" | No |
| <a id="opt-providers-kubernetesGateway-throttleDuration" href="#opt-providers-kubernetesGateway-throttleDuration" title="#opt-providers-kubernetesGateway-throttleDuration">`providers.kubernetesGateway.throttleDuration`</a> | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
| <a id="opt-providers-kubernetesGateway-nativeLBByDefault" href="#opt-providers-kubernetesGateway-nativeLBByDefault" title="#opt-providers-kubernetesGateway-nativeLBByDefault">`providers.kubernetesGateway.nativeLBByDefault`</a> | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No |
| <a id="opt-providers-kubernetesGateway-retryMaxRequestBodyBytes" href="#opt-providers-kubernetesGateway-retryMaxRequestBodyBytes" title="#opt-providers-kubernetesGateway-retryMaxRequestBodyBytes">`providers.kubernetesGateway.retryMaxRequestBodyBytes`</a> | Defines the maximum request body size (in bytes) buffered for HTTPRoute retries.<br />If negative, the limit is disabled.<br />A request with a larger body is forwarded without retry. | 2097152 | No |
| <a id="opt-providers-kubernetesGateway-statusAddress-hostname" href="#opt-providers-kubernetesGateway-statusAddress-hostname" title="#opt-providers-kubernetesGateway-statusAddress-hostname">`providers.kubernetesGateway.`<br />`statusAddress.hostname`</a> | Hostname copied to the Gateway `status.addresses`. | "" | No |
| <a id="opt-providers-kubernetesGateway-statusAddress-ip" href="#opt-providers-kubernetesGateway-statusAddress-ip" title="#opt-providers-kubernetesGateway-statusAddress-ip">`providers.kubernetesGateway.`<br />`statusAddress.ip`</a> | IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
| <a id="opt-providers-kubernetesGateway-statusAddress-service-namespace" href="#opt-providers-kubernetesGateway-statusAddress-service-namespace" title="#opt-providers-kubernetesGateway-statusAddress-service-namespace">`providers.kubernetesGateway.`<br />`statusAddress.service.namespace`</a> | The namespace of the Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No |
@@ -200,6 +200,7 @@
throttleDuration = "42s"
experimentalChannel = true
nativeLBByDefault = true
retryMaxRequestBodyBytes = 42
[providers.kubernetesGateway.statusAddress]
ip = "foobar"
hostname = "foobar"
@@ -226,6 +226,7 @@ providers:
name: foobar
namespace: foobar
nativeLBByDefault: true
retryMaxRequestBodyBytes: 42
rest:
insecure: true
consulCatalog:
@@ -55,7 +55,7 @@ profiles:
result: success
statistics:
Failed: 0
Passed: 56
Passed: 60
Skipped: 0
supportedFeatures:
- BackendTLSPolicy
@@ -89,6 +89,8 @@ profiles:
- HTTPRouteQueryParamMatching
- HTTPRouteRequestTimeout
- HTTPRouteResponseHeaderModification
- HTTPRouteRetryCodes
- HTTPRouteRetryConnectionError
- HTTPRouteSchemeRedirect
- ListenerSet
unsupportedFeatures:
@@ -100,8 +102,6 @@ profiles:
- HTTPRouteRequestMirror
- HTTPRouteRequestMultipleMirrors
- HTTPRouteRequestPercentageMirror
- HTTPRouteRetryCodes
- HTTPRouteRetryConnectionError
name: GATEWAY-HTTP
summary: Core tests succeeded. Extended tests succeeded.
- core:
@@ -176,3 +176,5 @@ succeededProvisionalTests:
- GatewayPreserveForeignConditions
- HTTPRouteBackendURLRewrite
- HTTPRoutePreserveForeignStatus
- HTTPRouteRetryWithBackendTimeout
- HTTPRouteRetryWithRequestTimeout
@@ -68,7 +68,7 @@ profiles:
- GatewayInvalidFrontendClientCertificateValidation
statistics:
Failed: 0
Passed: 49
Passed: 53
Skipped: 5
supportedFeatures:
- BackendTLSPolicy
@@ -100,6 +100,8 @@ profiles:
- HTTPRouteQueryParamMatching
- HTTPRouteRequestTimeout
- HTTPRouteResponseHeaderModification
- HTTPRouteRetryCodes
- HTTPRouteRetryConnectionError
- HTTPRouteSchemeRedirect
- ListenerSet
unsupportedFeatures:
@@ -113,8 +115,6 @@ profiles:
- HTTPRouteRequestMirror
- HTTPRouteRequestMultipleMirrors
- HTTPRouteRequestPercentageMirror
- HTTPRouteRetryCodes
- HTTPRouteRetryConnectionError
name: GATEWAY-HTTP
summary: Core tests partially succeeded with 1 test skips. Extended tests partially
succeeded with 5 test skips.
@@ -197,3 +197,5 @@ succeededProvisionalTests:
- GatewayPreserveForeignConditions
- HTTPRouteBackendURLRewrite
- HTTPRoutePreserveForeignStatus
- HTTPRouteRetryWithBackendTimeout
- HTTPRouteRetryWithRequestTimeout
@@ -74,6 +74,8 @@ func extendedHTTPRouteFeatures() sets.Set[features.Feature] {
features.HTTPRouteRequestTimeoutFeature,
features.HTTPRouteBackendTimeoutFeature,
features.HTTPRouteBackendURLRewriteFeature,
features.HTTPRouteRetryCodesFeature,
features.HTTPRouteRetryConnectionErrorFeature,
)
}
@@ -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: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "example.org"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
set:
- name: X-Foo
value: Bar
add:
- name: X-Bar
value: Foo
remove:
- X-Baz
retry:
codes:
- 500
- 503
attempts: 3
backoff: 100ms
@@ -0,0 +1,52 @@
---
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: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "example.org"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
retry: {}
@@ -158,6 +158,11 @@ func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName,
}
}
var retry *dynamic.Middleware
if routeRule.Retry != nil {
retry = createRetry(ctx, routeRule.Retry, p.RetryMaxRequestBodyBytes)
}
for _, match := range routeRule.Matches {
rule, priority := buildMatchRule(hostnames, match)
router := dynamic.Router{
@@ -223,6 +228,13 @@ func (p *Provider) loadHTTPRouteConfiguration(ctx context.Context, gatewayName,
}
}
if err == nil && retry != nil {
// The retry middleware comes last, so that only the call to the backend is retried.
retryName := routerName + "-retry"
routerConf.HTTP.Middlewares[retryName] = retry
router.Middlewares = append(router.Middlewares, retryName)
}
p.applyRouterTransform(ctx, &router, route)
routerConf.HTTP.Routers[routerName] = &router
@@ -1088,6 +1100,30 @@ func createCORS(filter *gatev1.HTTPCORSFilter) *dynamic.Middleware {
}
}
func createRetry(ctx context.Context, retry *gatev1.HTTPRouteRetry, maxRequestBodyBytes int64) *dynamic.Middleware {
// The Gateway API attempts are the number of retries,
// whereas the Traefik ones also count the initial request, hence the +1.
r := &dynamic.Retry{
Attempts: ptr.Deref(retry.Attempts, 1) + 1,
MaxRequestBodyBytes: &maxRequestBodyBytes,
}
for _, code := range retry.Codes {
r.Status = append(r.Status, strconv.Itoa(int(code)))
}
if retry.Backoff != nil {
// The CRD validation guarantees a valid GEP-2257 duration string.
if backoff, err := time.ParseDuration(string(*retry.Backoff)); err != nil {
log.Ctx(ctx).Error().Err(err).Msg("Ignoring HTTPRoute retry.backoff: invalid duration")
} else {
r.InitialInterval = ptypes.Duration(backoff)
}
}
return &dynamic.Middleware{Retry: r}
}
func getHTTPServiceProtocol(portSpec corev1.ServicePort) (string, error) {
if portSpec.Protocol != corev1.ProtocolTCP {
return "", errors.New("only TCP protocol is supported")
@@ -92,13 +92,14 @@ type Provider struct {
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
GatewayClass string `description:"Name of the GatewayClass to watch for. If empty, all GatewayClasses are processed." json:"gatewayClass,omitempty" toml:"gatewayClass,omitempty" yaml:"gatewayClass,omitempty" export:"true"`
// Deprecated: please use the GatewayClass option instead.
LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses (Deprecated, please use the GatewayClass option instead)." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
Gateways []NamespacedName `description:"Scopes the provider to specific Gateways." json:"gateways,omitempty" toml:"gateways,omitempty" yaml:"gateways,omitempty" export:"true"`
ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
ExperimentalChannel bool `description:"Toggles Experimental Channel resources support. Requires the Experimental Channel CRDs." json:"experimentalChannel,omitempty" toml:"experimentalChannel,omitempty" yaml:"experimentalChannel,omitempty" export:"true"`
StatusAddress *StatusAddress `description:"Defines the Kubernetes Gateway status address." json:"statusAddress,omitempty" toml:"statusAddress,omitempty" yaml:"statusAddress,omitempty" export:"true"`
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
CrossProviderNamespaces []string `description:"List of namespaces from which Gateway API routes are allowed to declare TraefikService backendRef references." json:"crossProviderNamespaces,omitempty" toml:"crossProviderNamespaces,omitempty" yaml:"crossProviderNamespaces,omitempty" export:"true"`
LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses (Deprecated, please use the GatewayClass option instead)." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
Gateways []NamespacedName `description:"Scopes the provider to specific Gateways." json:"gateways,omitempty" toml:"gateways,omitempty" yaml:"gateways,omitempty" export:"true"`
ThrottleDuration ptypes.Duration `description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
ExperimentalChannel bool `description:"Toggles Experimental Channel resources support. Requires the Experimental Channel CRDs." json:"experimentalChannel,omitempty" toml:"experimentalChannel,omitempty" yaml:"experimentalChannel,omitempty" export:"true"`
StatusAddress *StatusAddress `description:"Defines the Kubernetes Gateway status address." json:"statusAddress,omitempty" toml:"statusAddress,omitempty" yaml:"statusAddress,omitempty" export:"true"`
NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"`
CrossProviderNamespaces []string `description:"List of namespaces from which Gateway API routes are allowed to declare TraefikService backendRef references." json:"crossProviderNamespaces,omitempty" toml:"crossProviderNamespaces,omitempty" yaml:"crossProviderNamespaces,omitempty" export:"true"`
RetryMaxRequestBodyBytes int64 `description:"Defines the maximum request body size (in bytes) buffered for HTTPRoute retries. If negative, the limit is disabled." json:"retryMaxRequestBodyBytes,omitempty" toml:"retryMaxRequestBodyBytes,omitempty" yaml:"retryMaxRequestBodyBytes,omitempty" export:"true"`
EntryPoints map[string]Entrypoint `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
@@ -114,6 +115,7 @@ type Provider struct {
func (p *Provider) SetDefaults() {
p.QPS = 50 // the default value for the QPS is 10x the default Kubernetes client QPS value.
p.Burst = 100 // the default value for the Burst is 10x the default Kubernetes client Burst value.
p.RetryMaxRequestBodyBytes = dynamic.RetryDefaultMaxRequestBodyBytes
}
// Entrypoint defines the available entry points.
@@ -220,12 +220,13 @@ func TestGatewayScoping(t *testing.T) {
func TestLoadHTTPRoutes(t *testing.T) {
testCases := []struct {
desc string
ingressClass string
paths []string
expected *dynamic.Configuration
entryPoints map[string]Entrypoint
nativeLB bool
desc string
ingressClass string
paths []string
expected *dynamic.Configuration
entryPoints map[string]Entrypoint
nativeLB bool
retryMaxRequestBodyBytes int64
}{
{
desc: "Empty",
@@ -2880,6 +2881,255 @@ func TestLoadHTTPRoutes(t *testing.T) {
},
},
},
{
desc: "Simple HTTPRoute, retry",
paths: []string{"services.yml", "httproute/with_retry.yml"},
retryMaxRequestBodyBytes: dynamic.RetryDefaultMaxRequestBodyBytes,
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-5b3226d4ebd42b7200f1": {
EntryPoints: []string{"web"},
Rule: `Host("*")`,
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664": {
ParentRefs: []string{"listener-web-http-5b3226d4ebd42b7200f1"},
Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-wrr",
Rule: `Host("example.org") && PathPrefix("/")`,
Priority: 13,
RuleSyntax: "default",
Middlewares: []string{"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-requestheadermodifier-0", "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-retry"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-requestheadermodifier-0": {
RequestHeaderModifier: &dynamic.HeaderModifier{
Set: map[string]string{"X-Foo": "Bar"},
Add: map[string]string{"X-Bar": "Foo"},
Remove: []string{"X-Baz"},
},
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-retry": {
Retry: &dynamic.Retry{
Attempts: 4,
Status: []string{"500", "503"},
InitialInterval: ptypes.Duration(100 * time.Millisecond),
MaxRequestBodyBytes: new(dynamic.RetryDefaultMaxRequestBodyBytes),
},
},
},
Services: map[string]*dynamic.Service{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-svc-default-whoami-0",
Weight: new(1),
},
},
},
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-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{},
},
},
},
{
desc: "Simple HTTPRoute, retry with maxRequestBodyBytes",
paths: []string{"services.yml", "httproute/with_retry.yml"},
retryMaxRequestBodyBytes: 1024,
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-5b3226d4ebd42b7200f1": {
EntryPoints: []string{"web"},
Rule: `Host("*")`,
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664": {
ParentRefs: []string{"listener-web-http-5b3226d4ebd42b7200f1"},
Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-wrr",
Rule: `Host("example.org") && PathPrefix("/")`,
Priority: 13,
RuleSyntax: "default",
Middlewares: []string{"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-requestheadermodifier-0", "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-retry"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-requestheadermodifier-0": {
RequestHeaderModifier: &dynamic.HeaderModifier{
Set: map[string]string{"X-Foo": "Bar"},
Add: map[string]string{"X-Bar": "Foo"},
Remove: []string{"X-Baz"},
},
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-retry": {
Retry: &dynamic.Retry{
Attempts: 4,
Status: []string{"500", "503"},
InitialInterval: ptypes.Duration(100 * time.Millisecond),
MaxRequestBodyBytes: new(int64(1024)),
},
},
},
Services: map[string]*dynamic.Service{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-svc-default-whoami-0",
Weight: new(1),
},
},
},
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-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{},
},
},
},
{
desc: "Simple HTTPRoute, retry with defaults",
paths: []string{"services.yml", "httproute/with_retry_empty.yml"},
retryMaxRequestBodyBytes: dynamic.RetryDefaultMaxRequestBodyBytes,
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-5b3226d4ebd42b7200f1": {
EntryPoints: []string{"web"},
Rule: `Host("*")`,
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664": {
ParentRefs: []string{"listener-web-http-5b3226d4ebd42b7200f1"},
Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-wrr",
Rule: `Host("example.org") && PathPrefix("/")`,
Priority: 13,
RuleSyntax: "default",
Middlewares: []string{"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-retry"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-retry": {
Retry: &dynamic.Retry{
Attempts: 2,
MaxRequestBodyBytes: new(dynamic.RetryDefaultMaxRequestBodyBytes),
},
},
},
Services: map[string]*dynamic.Service{
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-svc-default-whoami-0",
Weight: new(1),
},
},
},
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-c683caccb22c43e5e664-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{},
},
},
},
{
desc: "Simple HTTPRoute, response header modifier",
paths: []string{"services.yml", "httproute/filter_response_header_modifier.yml"},
@@ -4894,9 +5144,10 @@ func TestLoadHTTPRoutes(t *testing.T) {
}
p := Provider{
EntryPoints: test.entryPoints,
NativeLBByDefault: test.nativeLB,
client: client,
EntryPoints: test.entryPoints,
NativeLBByDefault: test.nativeLB,
RetryMaxRequestBodyBytes: test.retryMaxRequestBodyBytes,
client: client,
}
conf, _, err := p.loadConfigurationFromGateways(t.Context())