From d37ea3e88256b130126efd16d9c59465c95e08fe Mon Sep 17 00:00:00 2001 From: Fahrzin Hemmati Date: Mon, 29 Jan 2024 01:58:05 -0800 Subject: [PATCH] Add ResponseCode to CircuitBreaker --- docs/content/middlewares/http/circuitbreaker.md | 7 +++++++ .../reference/dynamic-configuration/docker-labels.yml | 1 + docs/content/reference/dynamic-configuration/file.toml | 1 + docs/content/reference/dynamic-configuration/file.yaml | 1 + docs/content/reference/dynamic-configuration/kv-ref.md | 1 + pkg/config/dynamic/middlewares.go | 4 ++++ pkg/config/label/label_test.go | 4 ++++ pkg/middlewares/circuitbreaker/circuit_breaker.go | 6 ++++-- pkg/provider/kv/kv_test.go | 2 ++ 9 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/content/middlewares/http/circuitbreaker.md b/docs/content/middlewares/http/circuitbreaker.md index be7b1422d..c1ead36b6 100644 --- a/docs/content/middlewares/http/circuitbreaker.md +++ b/docs/content/middlewares/http/circuitbreaker.md @@ -85,6 +85,7 @@ At specified intervals (`checkPeriod`), the circuit breaker evaluates `expressio ### Open While open, the fallback mechanism takes over the normal service calls for a duration of `FallbackDuration`. +The fallback mechanism returns a `HTTP 503` (or `ResponseCode`) to the client. After this duration, it enters the recovering state. ### Recovering @@ -179,3 +180,9 @@ The duration for which the circuit breaker will wait before trying to recover (f _Optional, Default="10s"_ The duration for which the circuit breaker will try to recover (as soon as it is in recovering state). + +### `ResponseCode` + +_Optional, Default="503"_ + +The status code that the circuit breaker will return while it is in the open state. diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 7044d13d0..3d1eb62f5 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -16,6 +16,7 @@ - "traefik.http.middlewares.middleware05.circuitbreaker.expression=foobar" - "traefik.http.middlewares.middleware05.circuitbreaker.fallbackduration=42s" - "traefik.http.middlewares.middleware05.circuitbreaker.recoveryduration=42s" +- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42" - "traefik.http.middlewares.middleware06.compress=true" - "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 7d771c6a2..b4565206b 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -137,6 +137,7 @@ checkPeriod = "42s" fallbackDuration = "42s" recoveryDuration = "42s" + responseCode = 42 [http.middlewares.Middleware06] [http.middlewares.Middleware06.compress] excludedContentTypes = ["foobar", "foobar"] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 24df9aada..430e8cab1 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -142,6 +142,7 @@ http: checkPeriod: 42s fallbackDuration: 42s recoveryDuration: 42s + responseCode: 42 Middleware06: compress: excludedContentTypes: diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index cc0334567..6709fe5a9 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -20,6 +20,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/middlewares/Middleware05/circuitBreaker/expression` | `foobar` | | `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` | | `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` | | `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` | | `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` | diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index a2c1794eb..fd1c20d33 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -1,6 +1,7 @@ package dynamic import ( + "net/http" "time" ptypes "github.com/traefik/paerser/types" @@ -141,6 +142,8 @@ type CircuitBreaker struct { FallbackDuration ptypes.Duration `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"` // RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state). RecoveryDuration ptypes.Duration `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"` + // ResponseCode is the status code that the circuit breaker will return while it is in the open state. + ResponseCode int `json:"responseCode,omitempty" toml:"responseCode,omitempty" yaml:"responseCode,omitempty" export:"true"` } // SetDefaults sets the default values on a RateLimit. @@ -148,6 +151,7 @@ func (c *CircuitBreaker) SetDefaults() { c.CheckPeriod = ptypes.Duration(100 * time.Millisecond) c.FallbackDuration = ptypes.Duration(10 * time.Second) c.RecoveryDuration = ptypes.Duration(10 * time.Second) + c.ResponseCode = http.StatusServiceUnavailable } // +k8s:deepcopy-gen=true diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 28606cdbc..79f3df14b 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -30,6 +30,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.checkperiod": "1s", "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.fallbackduration": "1s", "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.recoveryduration": "1s", + "traefik.HTTP.Middlewares.Middleware4.circuitbreaker.responsecode": "403", "traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar", "traefik.http.middlewares.Middleware5.digestauth.realm": "foobar", "traefik.http.middlewares.Middleware5.digestauth.removeheader": "true", @@ -496,6 +497,7 @@ func TestDecodeConfiguration(t *testing.T) { CheckPeriod: ptypes.Duration(time.Second), FallbackDuration: ptypes.Duration(time.Second), RecoveryDuration: ptypes.Duration(time.Second), + ResponseCode: 403, }, }, "Middleware5": { @@ -996,6 +998,7 @@ func TestEncodeConfiguration(t *testing.T) { CheckPeriod: ptypes.Duration(time.Second), FallbackDuration: ptypes.Duration(time.Second), RecoveryDuration: ptypes.Duration(time.Second), + ResponseCode: 404, }, }, "Middleware5": { @@ -1206,6 +1209,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.CheckPeriod": "1000000000", "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.FallbackDuration": "1000000000", "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.RecoveryDuration": "1000000000", + "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.ResponseCode": "404", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true", diff --git a/pkg/middlewares/circuitbreaker/circuit_breaker.go b/pkg/middlewares/circuitbreaker/circuit_breaker.go index 3fa06ba23..ba377ea3d 100644 --- a/pkg/middlewares/circuitbreaker/circuit_breaker.go +++ b/pkg/middlewares/circuitbreaker/circuit_breaker.go @@ -30,12 +30,14 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ logger.Debug().Msg("Creating middleware") logger.Debug().Msgf("Setting up with expression: %s", expression) + responseCode := confCircuitBreaker.ResponseCode + cbOpts := []cbreaker.Option{ cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { tracing.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression) - rw.WriteHeader(http.StatusServiceUnavailable) + rw.WriteHeader(responseCode) - if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil { + if _, err := rw.Write([]byte(http.StatusText(responseCode))); err != nil { log.Ctx(req.Context()).Error().Err(err).Send() } })), diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index cfe901037..8f63b5cb1 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -171,6 +171,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod": "1s", "traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration": "1s", "traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration": "1s", + "traefik/http/middlewares/Middleware04/circuitBreaker/responseCode": "404", "traefik/http/middlewares/Middleware07/errors/status/0": "foobar", "traefik/http/middlewares/Middleware07/errors/status/1": "foobar", "traefik/http/middlewares/Middleware07/errors/service": "foobar", @@ -392,6 +393,7 @@ func Test_buildConfiguration(t *testing.T) { CheckPeriod: ptypes.Duration(time.Second), FallbackDuration: ptypes.Duration(time.Second), RecoveryDuration: ptypes.Duration(time.Second), + ResponseCode: 404, }, }, "Middleware05": {