mirror of
https://github.com/traefik/traefik.git
synced 2025-05-05 15:33:01 +00:00
Merge branch v2.11 into v3.3
This commit is contained in:
commit
32ea014d07
2
.github/workflows/validate.yaml
vendored
2
.github/workflows/validate.yaml
vendored
@ -7,7 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.23'
|
||||
GOLANGCI_LINT_VERSION: v1.63.3
|
||||
GOLANGCI_LINT_VERSION: v1.64.2
|
||||
MISSPELL_VERSION: v0.6.0
|
||||
|
||||
jobs:
|
||||
|
@ -1,5 +1,6 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
relative-path-mode: cfg
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
@ -165,6 +166,7 @@ linters-settings:
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- tenv # Deprecated
|
||||
- sqlclosecheck # not relevant (SQL)
|
||||
- rowserrcheck # not relevant (SQL)
|
||||
- cyclop # duplicate of gocyclo
|
||||
@ -201,7 +203,6 @@ linters:
|
||||
- maintidx # kind of duplicate of gocyclo
|
||||
- nonamedreturns # Too strict
|
||||
- gosmopolitan # not relevant
|
||||
- exportloopref # Not relevant since go1.22
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
|
@ -1,3 +1,12 @@
|
||||
# [v2.11.21](https://github.com/traefik/traefik/tree/v2.11.21) (2025-02-24)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.20...v2.11.21)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Bump github.com/go-acme/lego/v4 to v4.22.2 ([#11537](https://github.com/traefik/traefik/pull/11537) by [ldez](https://github.com/ldez))
|
||||
- **[cli]** Bump github.com/traefik/paerser to v0.2.2 ([#11530](https://github.com/traefik/traefik/pull/11530) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[middleware]** Enable the retry middleware in the proxy ([#11536](https://github.com/traefik/traefik/pull/11536) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[middleware]** Retry should send headers on Write ([#11534](https://github.com/traefik/traefik/pull/11534) by [kevinpollet](https://github.com/kevinpollet))
|
||||
|
||||
## [v3.3.3](https://github.com/traefik/traefik/tree/v3.3.3) (2025-01-31)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.3.2...v3.3.3)
|
||||
|
||||
|
@ -316,7 +316,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
|
||||
| Provider Name | Provider Code | Environment Variables | |
|
||||
|------------------------------------------------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH`, `ACME_DNS_STORAGE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
||||
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
||||
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
|
||||
| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
|
||||
@ -397,6 +397,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
|
||||
| [mijn.host](https://mijn.host/) | `mijnhost` | `MIJNHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/mijnhost) |
|
||||
| [Mittwald](https://www.mittwald.de) | `mittwald` | `MITTWALD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/mittwald) |
|
||||
| [myaddr.{tools,dev,io}](https://myaddr.tools/) | `myaddr` | `MYADDR_PRIVATE_KEYS_MAPPING` | [Additional configuration](https://go-acme.github.io/lego/dns/myaddr) |
|
||||
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
||||
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
||||
@ -434,6 +435,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
||||
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
|
||||
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
||||
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
|
||||
| [Spaceship](https://spaceship.com) | `spaceship` | `SPACESHIP_API_KEY`, `SPACESHIP_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/spaceship) |
|
||||
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
|
||||
| [Technitium](https://technitium.com) | `technitium` | `TECHNITIUM_SERVER_BASE_URL`, `TECHNITIUM_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/technitium) |
|
||||
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
|
||||
|
8
go.mod
8
go.mod
@ -17,7 +17,7 @@ require (
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/go-acme/lego/v4 v4.21.0
|
||||
github.com/go-acme/lego/v4 v4.22.2
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/golang/protobuf v1.5.4
|
||||
@ -63,7 +63,7 @@ require (
|
||||
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/paerser v0.2.1
|
||||
github.com/traefik/paerser v0.2.2
|
||||
github.com/traefik/yaegi v0.16.1
|
||||
github.com/unrolled/render v1.0.2
|
||||
github.com/unrolled/secure v1.0.9
|
||||
@ -87,7 +87,6 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/log v0.8.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.28.0
|
||||
go.opentelemetry.io/otel/trace v1.32.0
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // No tag on the repo.
|
||||
golang.org/x/mod v0.22.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/sync v0.10.0
|
||||
@ -169,7 +168,6 @@ require (
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||
@ -281,6 +279,7 @@ require (
|
||||
github.com/nrdcg/desec v0.10.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.3.0 // indirect
|
||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
@ -361,6 +360,7 @@ require (
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||
golang.org/x/oauth2 v0.24.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
google.golang.org/api v0.214.0 // indirect
|
||||
|
12
go.sum
12
go.sum
@ -259,8 +259,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
@ -357,8 +355,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
|
||||
github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
|
||||
github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
|
||||
github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
@ -905,6 +903,8 @@ github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
|
||||
github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
|
||||
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
|
||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||
github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
|
||||
github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
|
||||
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
|
||||
@ -1187,8 +1187,8 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
|
||||
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
|
||||
github.com/traefik/paerser v0.2.1 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
|
||||
github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
|
||||
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||
github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
|
||||
|
57
integration/fixtures/acme/acme_domains_1712362990.toml
Normal file
57
integration/fixtures/acme/acme_domains_1712362990.toml
Normal file
@ -0,0 +1,57 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
noColor = true
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":5002"
|
||||
[entryPoints.websecure]
|
||||
address = ":5001"
|
||||
|
||||
|
||||
|
||||
[certificatesResolvers.default.acme]
|
||||
email = "test@traefik.io"
|
||||
storage = "/tmp/acme.json"
|
||||
keyType = ""
|
||||
caServer = "https://172.31.42.2:14000/dir"
|
||||
|
||||
|
||||
|
||||
|
||||
[certificatesResolvers.default.acme.tlsChallenge]
|
||||
|
||||
|
||||
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "fixtures/acme/acme_domains_1712362990.toml"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.services]
|
||||
[http.services.test.loadBalancer]
|
||||
[[http.services.test.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9010"
|
||||
|
||||
[http.routers]
|
||||
[http.routers.test]
|
||||
entryPoints = ["websecure"]
|
||||
rule = "PathPrefix(`/`)"
|
||||
service = "test"
|
||||
[http.routers.test.tls]
|
||||
certResolver = "default"
|
||||
|
||||
[[http.routers.test.tls.domains]]
|
||||
main = "acme.wtf"
|
||||
sans = [
|
||||
"traefik.acme.wtf",
|
||||
]
|
||||
|
@ -50,7 +50,7 @@
|
||||
Rule = "Path(`/basic`)"
|
||||
[http.routers.router1]
|
||||
Service = "service1"
|
||||
Middlewares = ["retry", "ratelimit-1"]
|
||||
Middlewares = ["ratelimit-1"]
|
||||
Rule = "Path(`/ratelimit`)"
|
||||
[http.routers.router2]
|
||||
Service = "service2"
|
||||
@ -58,8 +58,12 @@
|
||||
Rule = "Path(`/retry`)"
|
||||
[http.routers.router3]
|
||||
Service = "service3"
|
||||
Middlewares = ["retry", "basic-auth"]
|
||||
Middlewares = ["basic-auth"]
|
||||
Rule = "Path(`/auth`)"
|
||||
[http.routers.router4]
|
||||
Service = "service4"
|
||||
Middlewares = ["retry", "basic-auth"]
|
||||
Rule = "Path(`/retry-auth`)"
|
||||
[http.routers.customPing]
|
||||
entryPoints = ["web"]
|
||||
rule = "PathPrefix(`/ping`)"
|
||||
@ -98,3 +102,9 @@
|
||||
passHostHeader = true
|
||||
[[http.services.service3.loadBalancer.servers]]
|
||||
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"
|
||||
|
||||
[http.services.service4]
|
||||
[http.services.service4.loadBalancer]
|
||||
passHostHeader = true
|
||||
[[http.services.service4.loadBalancer.servers]]
|
||||
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"
|
||||
|
@ -77,7 +77,7 @@ func (s *TracingSuite) TearDownTest() {
|
||||
s.composeStop("tempo")
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
|
||||
func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
@ -144,7 +144,7 @@ func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
|
||||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
|
||||
func (s *TracingSuite) TestOpenTelemetryBasic_gRPC() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
@ -201,7 +201,7 @@ func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
|
||||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpentelemetryRateLimit() {
|
||||
func (s *TracingSuite) TestOpenTelemetryRateLimit() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
@ -248,48 +248,26 @@ func (s *TracingSuite) TestOpentelemetryRateLimit() {
|
||||
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.1.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.1.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.2.name": "RateLimiter",
|
||||
"batches.0.scopeSpans.0.spans.2.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.3.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.resend_count\").value.intValue": "1",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.4.name": "RateLimiter",
|
||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.5.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.resend_count\").value.intValue": "2",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.6.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.7.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.8.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.response.status_code\").value.intValue": "429",
|
||||
"batches.0.scopeSpans.0.spans.3.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "429",
|
||||
},
|
||||
{
|
||||
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
|
||||
@ -318,37 +296,33 @@ func (s *TracingSuite) TestOpentelemetryRateLimit() {
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.4.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.4.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.5.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.5.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.6.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.7.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
||||
"batches.0.scopeSpans.0.spans.6.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
||||
},
|
||||
}
|
||||
|
||||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpentelemetryRetry() {
|
||||
func (s *TracingSuite) TestOpenTelemetryRetry() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: 81,
|
||||
@ -471,7 +445,7 @@ func (s *TracingSuite) TestOpentelemetryRetry() {
|
||||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpentelemetryAuth() {
|
||||
func (s *TracingSuite) TestOpenTelemetryAuth() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
@ -498,59 +472,90 @@ func (s *TracingSuite) TestOpentelemetryAuth() {
|
||||
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
|
||||
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.1.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.1.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.2.name": "BasicAuth",
|
||||
"batches.0.scopeSpans.0.spans.2.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
||||
"batches.0.scopeSpans.0.spans.2.status.message": "Authentication failed",
|
||||
"batches.0.scopeSpans.0.spans.2.status.code": "STATUS_CODE_ERROR",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.3.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.resend_count\").value.intValue": "1",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.4.name": "BasicAuth",
|
||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
||||
"batches.0.scopeSpans.0.spans.4.status.message": "Authentication failed",
|
||||
"batches.0.scopeSpans.0.spans.4.status.code": "STATUS_CODE_ERROR",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.5.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.resend_count\").value.intValue": "2",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.6.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.7.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.8.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
|
||||
"batches.0.scopeSpans.0.spans.3.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
|
||||
},
|
||||
}
|
||||
|
||||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpentelemetrySafeURL() {
|
||||
func (s *TracingSuite) TestOpenTelemetryAuthWithRetry() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
IP: s.otelCollectorIP,
|
||||
})
|
||||
defer os.Remove(file)
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
// wait for traefik
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/retry-auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
contains := []map[string]string{
|
||||
{
|
||||
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.0.name": "BasicAuth",
|
||||
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
||||
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
|
||||
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.1.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.2.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service4@file",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "router4@file",
|
||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"http.route\").value.stringValue": "Path(`/retry-auth`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.3.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.4.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.path\").value.stringValue": "/retry-auth",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
|
||||
},
|
||||
}
|
||||
|
||||
s.checkTraceContent(contains)
|
||||
}
|
||||
|
||||
func (s *TracingSuite) TestOpenTelemetrySafeURL() {
|
||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||
WhoamiIP: s.whoamiIP,
|
||||
WhoamiPort: s.whoamiPort,
|
||||
@ -593,30 +598,26 @@ func (s *TracingSuite) TestOpentelemetrySafeURL() {
|
||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.4.name": "Retry",
|
||||
"batches.0.scopeSpans.0.spans.4.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.5.name": "Router",
|
||||
"batches.0.scopeSpans.0.spans.5.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.6.name": "Metrics",
|
||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||
|
||||
"batches.0.scopeSpans.0.spans.7.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.query\").value.stringValue": "api_key=REDACTED",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
||||
"batches.0.scopeSpans.0.spans.6.name": "EntryPoint",
|
||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.query\").value.stringValue": "api_key=REDACTED",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -357,7 +356,7 @@ func sortByName[T orderedWithName](direction string, results []T) {
|
||||
})
|
||||
}
|
||||
|
||||
func sortByFunc[T orderedWithName, U constraints.Ordered](direction string, results []T, fn func(int) U) {
|
||||
func sortByFunc[T orderedWithName, U cmp.Ordered](direction string, results []T, fn func(int) U) {
|
||||
// Ascending
|
||||
if direction == ascendantSorting {
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
|
@ -36,6 +36,48 @@ type Listener interface {
|
||||
// each of them about a retry attempt.
|
||||
type Listeners []Listener
|
||||
|
||||
// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
|
||||
func (l Listeners) Retried(req *http.Request, attempt int) {
|
||||
for _, listener := range l {
|
||||
listener.Retried(req, attempt)
|
||||
}
|
||||
}
|
||||
|
||||
type shouldRetryContextKey struct{}
|
||||
|
||||
// ShouldRetry is a function allowing to enable/disable the retry middleware mechanism.
|
||||
type ShouldRetry func(shouldRetry bool)
|
||||
|
||||
// ContextShouldRetry returns the ShouldRetry function if it has been set by the Retry middleware in the chain.
|
||||
func ContextShouldRetry(ctx context.Context) ShouldRetry {
|
||||
f, _ := ctx.Value(shouldRetryContextKey{}).(ShouldRetry)
|
||||
return f
|
||||
}
|
||||
|
||||
// WrapHandler wraps a given http.Handler to inject the httptrace.ClientTrace in the request context when it is needed
|
||||
// by the retry middleware.
|
||||
func WrapHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if shouldRetry := ContextShouldRetry(req.Context()); shouldRetry != nil {
|
||||
shouldRetry(true)
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
WroteHeaders: func() {
|
||||
shouldRetry(false)
|
||||
},
|
||||
WroteRequest: func(httptrace.WroteRequestInfo) {
|
||||
shouldRetry(false)
|
||||
},
|
||||
}
|
||||
newCtx := httptrace.WithClientTrace(req.Context(), trace)
|
||||
next.ServeHTTP(rw, req.WithContext(newCtx))
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// retry is a middleware that retries requests.
|
||||
type retry struct {
|
||||
attempts int
|
||||
@ -101,19 +143,13 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
req = req.WithContext(tracingCtx)
|
||||
}
|
||||
|
||||
shouldRetry := attempts < r.attempts
|
||||
retryResponseWriter := newResponseWriter(rw, shouldRetry)
|
||||
remainAttempts := attempts < r.attempts
|
||||
retryResponseWriter := newResponseWriter(rw)
|
||||
|
||||
// Disable retries when the backend already received request data
|
||||
clientTrace := &httptrace.ClientTrace{
|
||||
WroteHeaders: func() {
|
||||
retryResponseWriter.DisableRetries()
|
||||
},
|
||||
WroteRequest: func(httptrace.WroteRequestInfo) {
|
||||
retryResponseWriter.DisableRetries()
|
||||
},
|
||||
var shouldRetry ShouldRetry = func(shouldRetry bool) {
|
||||
retryResponseWriter.SetShouldRetry(remainAttempts && shouldRetry)
|
||||
}
|
||||
newCtx := httptrace.WithClientTrace(req.Context(), clientTrace)
|
||||
newCtx := context.WithValue(req.Context(), shouldRetryContextKey{}, shouldRetry)
|
||||
|
||||
r.next.ServeHTTP(retryResponseWriter, req.Clone(newCtx))
|
||||
|
||||
@ -164,18 +200,10 @@ func (r *retry) newBackOff() backoff.BackOff {
|
||||
return b
|
||||
}
|
||||
|
||||
// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
|
||||
func (l Listeners) Retried(req *http.Request, attempt int) {
|
||||
for _, listener := range l {
|
||||
listener.Retried(req, attempt)
|
||||
}
|
||||
}
|
||||
|
||||
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) *responseWriter {
|
||||
func newResponseWriter(rw http.ResponseWriter) *responseWriter {
|
||||
return &responseWriter{
|
||||
responseWriter: rw,
|
||||
headers: make(http.Header),
|
||||
shouldRetry: shouldRetry,
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,8 +218,8 @@ func (r *responseWriter) ShouldRetry() bool {
|
||||
return r.shouldRetry
|
||||
}
|
||||
|
||||
func (r *responseWriter) DisableRetries() {
|
||||
r.shouldRetry = false
|
||||
func (r *responseWriter) SetShouldRetry(shouldRetry bool) {
|
||||
r.shouldRetry = shouldRetry
|
||||
}
|
||||
|
||||
func (r *responseWriter) Header() http.Header {
|
||||
@ -205,20 +233,14 @@ func (r *responseWriter) Write(buf []byte) (int, error) {
|
||||
if r.ShouldRetry() {
|
||||
return len(buf), nil
|
||||
}
|
||||
if !r.written {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return r.responseWriter.Write(buf)
|
||||
}
|
||||
|
||||
func (r *responseWriter) WriteHeader(code int) {
|
||||
if r.ShouldRetry() && code == http.StatusServiceUnavailable {
|
||||
// We get a 503 HTTP Status Code when there is no backend server in the pool
|
||||
// to which the request could be sent. Also, note that r.ShouldRetry()
|
||||
// will never return true in case there was a connection established to
|
||||
// the backend server and so we can be sure that the 503 was produced
|
||||
// inside Traefik already and we don't have to retry in this cases.
|
||||
r.DisableRetries()
|
||||
}
|
||||
|
||||
if r.ShouldRetry() || r.written {
|
||||
if r.shouldRetry || r.written {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -105,12 +105,21 @@ func TestRetry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryAttempts := 0
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
// This signals that a connection will be established with the backend
|
||||
// to enable the Retry middleware mechanism.
|
||||
shouldRetry := ContextShouldRetry(req.Context())
|
||||
if shouldRetry != nil {
|
||||
shouldRetry(true)
|
||||
}
|
||||
|
||||
retryAttempts++
|
||||
|
||||
if retryAttempts > test.amountFaultyEndpoints {
|
||||
// calls WroteHeaders on httptrace.
|
||||
_ = r.Write(io.Discard)
|
||||
// This signals that request headers have been sent to the backend.
|
||||
if shouldRetry != nil {
|
||||
shouldRetry(false)
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
return
|
||||
@ -152,27 +161,16 @@ func TestRetryEmptyServerList(t *testing.T) {
|
||||
assert.Equal(t, 0, retryListener.timesCalled)
|
||||
}
|
||||
|
||||
func TestRetryListeners(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
retryListeners := Listeners{&countingRetryListener{}, &countingRetryListener{}}
|
||||
|
||||
retryListeners.Retried(req, 1)
|
||||
retryListeners.Retried(req, 1)
|
||||
|
||||
for _, retryListener := range retryListeners {
|
||||
listener := retryListener.(*countingRetryListener)
|
||||
if listener.timesCalled != 2 {
|
||||
t.Errorf("retry listener was called %d time(s), want %d time(s)", listener.timesCalled, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
|
||||
attempt := 0
|
||||
expectedHeaderName := "X-Foo-Test-2"
|
||||
expectedHeaderValue := "bar"
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
shouldRetry := ContextShouldRetry(req.Context())
|
||||
if shouldRetry != nil {
|
||||
shouldRetry(true)
|
||||
}
|
||||
|
||||
headerName := fmt.Sprintf("X-Foo-Test-%d", attempt)
|
||||
rw.Header().Add(headerName, expectedHeaderValue)
|
||||
if attempt < 2 {
|
||||
@ -181,43 +179,54 @@ func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
// Request has been successfully written to backend
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
trace.WroteHeaders()
|
||||
shouldRetry(false)
|
||||
|
||||
// And we decide to answer to client
|
||||
// And we decide to answer to client.
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
|
||||
require.NoError(t, err)
|
||||
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
retry.ServeHTTP(responseRecorder, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
|
||||
res := httptest.NewRecorder()
|
||||
retry.ServeHTTP(res, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
|
||||
|
||||
headerValue := responseRecorder.Header().Get(expectedHeaderName)
|
||||
|
||||
// Validate if we have the correct header
|
||||
if headerValue != expectedHeaderValue {
|
||||
t.Errorf("Expected to have %s for header %s, got %s", expectedHeaderValue, expectedHeaderName, headerValue)
|
||||
}
|
||||
// The third header attempt is kept.
|
||||
headerValue := res.Header().Get("X-Foo-Test-2")
|
||||
assert.Equal(t, expectedHeaderValue, headerValue)
|
||||
|
||||
// Validate that we don't have headers from previous attempts
|
||||
for i := range attempt {
|
||||
headerName := fmt.Sprintf("X-Foo-Test-%d", i)
|
||||
headerValue = responseRecorder.Header().Get("headerName")
|
||||
headerValue = res.Header().Get(headerName)
|
||||
if headerValue != "" {
|
||||
t.Errorf("Expected no value for header %s, got %s", headerName, headerValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// countingRetryListener is a Listener implementation to count the times the Retried fn is called.
|
||||
type countingRetryListener struct {
|
||||
timesCalled int
|
||||
}
|
||||
func TestRetryShouldNotLooseHeadersOnWrite(t *testing.T) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Add("X-Foo-Test", "bar")
|
||||
|
||||
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
|
||||
l.timesCalled++
|
||||
// Request has been successfully written to backend.
|
||||
shouldRetry := ContextShouldRetry(req.Context())
|
||||
if shouldRetry != nil {
|
||||
shouldRetry(false)
|
||||
}
|
||||
// And we decide to answer to client without calling WriteHeader.
|
||||
_, err := rw.Write([]byte("bar"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
|
||||
require.NoError(t, err)
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
retry.ServeHTTP(res, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
|
||||
|
||||
headerValue := res.Header().Get("X-Foo-Test")
|
||||
assert.Equal(t, "bar", headerValue)
|
||||
}
|
||||
|
||||
func TestRetryWithFlush(t *testing.T) {
|
||||
@ -275,12 +284,24 @@ func TestRetryWebsocket(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryAttempts := 0
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
// This signals that a connection will be established with the backend
|
||||
// to enable the Retry middleware mechanism.
|
||||
shouldRetry := ContextShouldRetry(req.Context())
|
||||
if shouldRetry != nil {
|
||||
shouldRetry(true)
|
||||
}
|
||||
|
||||
retryAttempts++
|
||||
|
||||
if retryAttempts > test.amountFaultyEndpoints {
|
||||
// This signals that request headers have been sent to the backend.
|
||||
if shouldRetry != nil {
|
||||
shouldRetry(false)
|
||||
}
|
||||
|
||||
upgrader := websocket.Upgrader{}
|
||||
_, err := upgrader.Upgrade(rw, r, nil)
|
||||
_, err := upgrader.Upgrade(rw, req, nil)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
@ -387,3 +408,12 @@ func Test1xxResponses(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 0, retryListener.timesCalled)
|
||||
}
|
||||
|
||||
// countingRetryListener is a Listener implementation to count the times the Retried fn is called.
|
||||
type countingRetryListener struct {
|
||||
timesCalled int
|
||||
}
|
||||
|
||||
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
|
||||
l.timesCalled++
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
|
||||
"github.com/traefik/traefik/v3/pkg/proxy/httputil"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/server/cookie"
|
||||
@ -349,6 +350,10 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
|
||||
}
|
||||
// The retry wrapping must be done just before the proxy handler,
|
||||
// to make sure that the retry will not be triggered/disabled by
|
||||
// middlewares in the chain.
|
||||
proxy = retry.WrapHandler(proxy)
|
||||
|
||||
// Prevents from enabling observability for internal resources.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user