Repository: traefik/traefik (language: go)

Issue:
ConsulCatalog: Allow filtering "Warning" status service health from being registered
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you expect to see?

Currently Traefik's ConsulCatalog provider [registers services in the "warning" status](https://github.com/traefik/traefik/blob/4f6c15cc14cdedc34484c697994134959fdff493/pkg/provider/consulcatalog/config.go#L135) to the route table which may not be ideal for some use cases. Traefik used to have a [`strictChecks`](https://doc.traefik.io/traefik/v1.7/configuration/backends/consulcatalog/) param that ensured only "passing" services were included in the router.

Was there a reason why this was removed? Any chance it could be restored?


Discussion / hints:
Hello,

the option `strictChecks` has been added in the v1 after the creation of the provider inside the v2:
- https://github.com/traefik/traefik/pull/4537 (v1.7.13)
- https://github.com/traefik/traefik/pull/5395 (v2.1.0)

The option has therefore not been removed because it has simply never been added :smile: 
Hello @djenriquez,

Thanks for the proposition. We would love community support to address it.  

If you or another community member would like to build it, let us know and we will work with you to make sure you have all the information needed so that it can be merged.  

We prefer to work with our community members at the beginning of the design process so that we can make sure that we are aligned and can move quickly with the review and merge process.  Let us know here or create a PR before you start and we will work with you there.  

Don’t forget to check out the [contributor docs](https://doc.traefik.io/traefik/contributing/submitting-pull-requests/#before-you-submit-a-pull-request) and link the PR to this issue.
If not, we will leave the issue open to any interested community member. 
Hi @nmengin sounds like fun! Can take a look at getting this configuration work done. Ultimately I'm thinking of exposing a new config field, something like `RouteableStatus` which accept a CSV of Consul constrained checks `Critical`, `Warning`, and `Passing`. Whatever the user decides to define will be picked up in place of the conditional in the OP.

The default would be set to the current behavior, so if not set, would include `Warning` and `Passing`.

Thoughts on high-level implementation?
Hello @djenriquez,

Thank you for the proposition.
We are wondering if we really need to consider the `Critical` status.

At first, we thought managing both `Warning` and `Passing` statuses without adding an option to customize the list was enough. 

Could you expose the use you have in mind, please?
We may have missed some points that explain the need for the option.
It will be very useful to keep routing to `Critical` status services since a health check failing doesn't necessarily mean the service has failed – it could just mean it was intermittent. I have health checks that need to fail at least 10 times before it truly considered a failure and the service is automatically restarted, for example.
Hi @axsuul,

Thanks for your comment.

I'm unsure if routing to `Critical` status service is a good idea. To me, if the service health check is marked as `Critical`` it means that the service is not ready to receive traffic. 

I would prefer to introduce the `strictChecks` option as it was implemented in v1.7. 

If any community member can help us to move forward on it, we would love the help.

Hi @mmatur, apologies for the forgetting about this issue, we had forked and put in a quick fix but our team would definitely like a proper solution merged into main. I think this is something that myself or someone on my team can contribute to.

Initial proposal was to allow any value that Consul defines as a possible service health state. I do agree that services in a `critical` state should never be routed to, but we have maybe a personal opinion not to bias the technology on what one thinks should happen rather allow for any state that the technology allows.

That said, we are totally fine not to include `critical` as an allowed state if the Traefik teams feels strongly about that. 

I will go ahead and get this work prioritized for my team in the meantime.

Fix patch:
diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md
index 3c601da318..cdec6ae86c 100644
--- a/docs/content/providers/consul-catalog.md
+++ b/docs/content/providers/consul-catalog.md
@@ -714,6 +714,32 @@ providers:
 # ...
 ```
 
+### `strictChecks`
+
+_Optional, Default="passing,warning"_
+
+Define which [Consul Service health checks](https://developer.hashicorp.com/consul/docs/services/usage/checks#define-initial-health-check-status) are allowed to take on traffic.
+
+```yaml tab="File (YAML)"
+providers:
+  consulCatalog:
+    strictChecks: 
+      - "passing"
+      - "warning"
+    # ...
+```
+
+```toml tab="File (TOML)"
+[providers.consulCatalog]
+  strictChecks = ["passing", "warning"]
+  # ...
+```
+
+```bash tab="CLI"
+--providers.consulcatalog.strictChecks=passing,warning
+# ...
+```
+
 ### `watch`
 
 _Optional, Default=false_
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index 179e376c59..d78150a72a 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -537,6 +537,9 @@ Name of the Traefik service in Consul Catalog (needs to be registered via the or
 `--providers.consulcatalog.stale`:  
 Use stale consistency for catalog reads. (Default: ```false```)
 
+`--providers.consulcatalog.strictchecks`:  
+A list of service health statuses to allow taking traffic. (Default: ```passing, warning```)
+
 `--providers.consulcatalog.watch`:  
 Watch Consul API events. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index e2a2e73f64..7947b9ddd8 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -513,6 +513,9 @@ Name of the Traefik service in Consul Catalog (needs to be registered via the or
 `TRAEFIK_PROVIDERS_CONSULCATALOG_STALE`:  
 Use stale consistency for catalog reads. (Default: ```false```)
 
+`TRAEFIK_PROVIDERS_CONSULCATALOG_STRICTCHECKS`:  
+A list of service health statuses to allow taking traffic. (Default: ```passing, warning```)
+
 `TRAEFIK_PROVIDERS_CONSULCATALOG_WATCH`:  
 Watch Consul API events. (Default: ```false```)
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index e3de7aa3b4..504d7c9b6d 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -161,6 +161,7 @@
     connectByDefault = true
     serviceName = "foobar"
     watch = true
+    strictChecks = ["foobar", "foobar"]
     namespaces = ["foobar", "foobar"]
     [providers.consulCatalog.endpoint]
       address = "foobar"
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index bdf314c29a..ffb9d75d88 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -192,6 +192,9 @@ providers:
     connectByDefault: true
     serviceName: foobar
     watch: true
+    strictChecks:
+      - foobar
+      - foobar
     namespaces:
       - foobar
       - foobar
diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go
index a45696fd4a..98c320013d 100644
--- a/pkg/provider/consulcatalog/config.go
+++ b/pkg/provider/consulcatalog/config.go
@@ -132,8 +132,8 @@ func (p *Provider) keepContainer(ctx context.Context, item itemData) bool {
 		return false
 	}
 
-	if item.Status != api.HealthPassing && item.Status != api.HealthWarning {
-		logger.Debug().Msg("Filtering unhealthy or starting item")
+	if !p.includesHealthStatus(item.Status) {
+		logger.Debug().Msgf("Status %q is not included in the configured strictChecks of %q", item.Status, strings.Join(p.StrictChecks, ","))
 		return false
 	}
 
@@ -324,3 +324,8 @@ func getName(i itemData) string {
 	hasher.Write([]byte(strings.Join(tags, "")))
 	return provider.Normalize(fmt.Sprintf("%s-%d", i.Name, hasher.Sum64()))
 }
+
+// defaultStrictChecks returns the default healthchecks to allow an upstream to be registered a route for loadbalancers.
+func defaultStrictChecks() []string {
+	return []string{api.HealthPassing, api.HealthWarning}
+}
diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go
index 696214e0cd..55c24edb6b 100644
--- a/pkg/provider/consulcatalog/consul_catalog.go
+++ b/pkg/provider/consulcatalog/consul_catalog.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"strconv"
+	"strings"
 	"text/template"
 	"time"
 
@@ -88,6 +89,7 @@ type Configuration struct {
 	ConnectByDefault  bool            `description:"Consider every service as Connect capable by default." json:"connectByDefault,omitempty" toml:"connectByDefault,omitempty" yaml:"connectByDefault,omitempty" export:"true"`
 	ServiceName       string          `description:"Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually)." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
 	Watch             bool            `description:"Watch Consul API events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
+	StrictChecks      []string        `description:"A list of service health statuses to allow taking traffic." json:"strictChecks,omitempty" toml:"strictChecks,omitempty" yaml:"strictChecks,omitempty" export:"true"`
 }
 
 // SetDefaults sets the default values.
@@ -98,6 +100,7 @@ func (c *Configuration) SetDefaults() {
 	c.ExposedByDefault = true
 	c.DefaultRule = defaultTemplateRule
 	c.ServiceName = "traefik"
+	c.StrictChecks = defaultStrictChecks()
 }
 
 // Provider is the Consul Catalog provider implementation.
@@ -578,6 +581,21 @@ func (p *Provider) watchConnectTLS(ctx context.Context) error {
 	}
 }
 
+// includesHealthStatus returns true if the status passed in exists in the configured StrictChecks configuration. Statuses are case insensitive.
+func (p *Provider) includesHealthStatus(status string) bool {
+	for _, s := range p.StrictChecks {
+		// If the "any" status is included, assume all health checks are included
+		if strings.EqualFold(s, api.HealthAny) {
+			return true
+		}
+
+		if strings.EqualFold(s, status) {
+			return true
+		}
+	}
+	return false
+}
+
 func createClient(namespace string, endpoint *EndpointConfig) (*api.Client, error) {
 	config := api.Config{
 		Address:    endpoint.Address,


Test patch:
diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go
index 5370a75ca2..4649eb3bde 100644
--- a/pkg/provider/consulcatalog/config_test.go
+++ b/pkg/provider/consulcatalog/config_test.go
@@ -287,11 +287,13 @@ func TestDefaultRule(t *testing.T) {
 		t.Run(test.desc, func(t *testing.T) {
 			t.Parallel()
 
+			var config Configuration
+
+			config.SetDefaults()
+			config.DefaultRule = test.defaultRule
+
 			p := Provider{
-				Configuration: Configuration{
-					ExposedByDefault: true,
-					DefaultRule:      test.defaultRule,
-				},
+				Configuration: config,
 			}
 
 			err := p.Init()
@@ -3125,13 +3127,15 @@ func Test_buildConfiguration(t *testing.T) {
 		t.Run(test.desc, func(t *testing.T) {
 			t.Parallel()
 
+			var config Configuration
+
+			config.SetDefaults()
+			config.DefaultRule = "Host(`{{ normalize .Name }}.traefik.wtf`)"
+			config.ConnectAware = test.ConnectAware
+			config.Constraints = test.constraints
+
 			p := Provider{
-				Configuration: Configuration{
-					ExposedByDefault: true,
-					DefaultRule:      "Host(`{{ normalize .Name }}.traefik.wtf`)",
-					ConnectAware:     test.ConnectAware,
-					Constraints:      test.constraints,
-				},
+				Configuration: config,
 			}
 
 			err := p.Init()
@@ -3206,3 +3210,449 @@ func extractNSFromProvider(providers []*Provider) []string {
 	}
 	return res
 }
+
+func TestFilterHealthStatuses(t *testing.T) {
+	testCases := []struct {
+		desc         string
+		items        []itemData
+		strictChecks []string
+		expected     *dynamic.Configuration
+	}{
+		{
+			// No value passed in here, we assume the default of ["passing", "warning"]
+			desc:         "test default strict checks",
+			strictChecks: defaultStrictChecks(),
+			items: []itemData{
+				{
+					ID:      "id",
+					Node:    "Node1",
+					Name:    "Test1",
+					Address: "127.0.0.1",
+					Port:    "80",
+					Labels:  nil,
+					Status:  api.HealthPassing,
+				},
+				{
+					ID:      "id",
+					Node:    "Node2",
+					Name:    "Test2",
+					Address: "127.0.0.1",
+					Port:    "81",
+					Labels:  nil,
+					Status:  api.HealthWarning,
+				},
+			},
+			expected: &dynamic.Configuration{
+				TCP: &dynamic.TCPConfiguration{
+					Routers:           map[string]*dynamic.TCPRouter{},
+					Middlewares:       map[string]*dynamic.TCPMiddleware{},
+					Services:          map[string]*dynamic.TCPService{},
+					ServersTransports: map[string]*dynamic.TCPServersTransport{},
+				},
+				UDP: &dynamic.UDPConfiguration{
+					Routers:  map[string]*dynamic.UDPRouter{},
+					Services: map[string]*dynamic.UDPService{},
+				},
+				HTTP: &dynamic.HTTPConfiguration{
+					Routers: map[string]*dynamic.Router{
+						"Test1": {
+							Service:     "Test1",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+						"Test2": {
+							Service:     "Test2",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+					},
+					Middlewares: map[string]*dynamic.Middleware{},
+					Services: map[string]*dynamic.Service{
+						"Test1": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:80",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+						"Test2": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:81",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+					},
+					ServersTransports: map[string]*dynamic.ServersTransport{},
+				},
+			},
+		},
+		{
+			// The item's health status is not included in the default checks, do not expect any containers
+			desc:         "test status not included",
+			strictChecks: defaultStrictChecks(),
+			items: []itemData{
+				{
+					ID:      "id",
+					Node:    "Node1",
+					Name:    "Test",
+					Address: "127.0.0.1",
+					Port:    "80",
+					Labels:  nil,
+					Status:  api.HealthCritical,
+				},
+			},
+			expected: &dynamic.Configuration{
+				TCP: &dynamic.TCPConfiguration{
+					Routers:           map[string]*dynamic.TCPRouter{},
+					Middlewares:       map[string]*dynamic.TCPMiddleware{},
+					Services:          map[string]*dynamic.TCPService{},
+					ServersTransports: map[string]*dynamic.TCPServersTransport{},
+				},
+				UDP: &dynamic.UDPConfiguration{
+					Routers:  map[string]*dynamic.UDPRouter{},
+					Services: map[string]*dynamic.UDPService{},
+				},
+				HTTP: &dynamic.HTTPConfiguration{
+					Routers:           map[string]*dynamic.Router{},
+					Middlewares:       map[string]*dynamic.Middleware{},
+					Services:          map[string]*dynamic.Service{},
+					ServersTransports: map[string]*dynamic.ServersTransport{},
+				},
+			},
+		},
+		{
+			// Allow only "warning" status containers to be included
+			desc:         "test only include warning",
+			strictChecks: []string{api.HealthWarning},
+			items: []itemData{
+				{
+					ID:      "id",
+					Node:    "Node1",
+					Name:    "Test1",
+					Address: "127.0.0.1",
+					Port:    "80",
+					Labels:  nil,
+					Status:  api.HealthPassing,
+				},
+				{
+					ID:      "id2",
+					Node:    "Node2",
+					Name:    "Test2",
+					Address: "127.0.0.1",
+					Port:    "81",
+					Labels:  nil,
+					Status:  api.HealthWarning,
+				},
+			},
+			expected: &dynamic.Configuration{
+				TCP: &dynamic.TCPConfiguration{
+					Routers:           map[string]*dynamic.TCPRouter{},
+					Middlewares:       map[string]*dynamic.TCPMiddleware{},
+					Services:          map[string]*dynamic.TCPService{},
+					ServersTransports: map[string]*dynamic.TCPServersTransport{},
+				},
+				UDP: &dynamic.UDPConfiguration{
+					Routers:  map[string]*dynamic.UDPRouter{},
+					Services: map[string]*dynamic.UDPService{},
+				},
+				HTTP: &dynamic.HTTPConfiguration{
+					Routers: map[string]*dynamic.Router{
+						"Test2": {
+							Service:     "Test2",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+					},
+					Middlewares: map[string]*dynamic.Middleware{},
+					Services: map[string]*dynamic.Service{
+						"Test2": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:81",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+					},
+					ServersTransports: map[string]*dynamic.ServersTransport{},
+				},
+			},
+		},
+		{
+			// Reject "critical" health status
+			desc:         "test critical status not included",
+			strictChecks: defaultStrictChecks(),
+			items: []itemData{
+				{
+					ID:      "id",
+					Node:    "Node1",
+					Name:    "Test1",
+					Address: "127.0.0.1",
+					Port:    "80",
+					Labels:  nil,
+					Status:  api.HealthPassing,
+				},
+				{
+					ID:      "id2",
+					Node:    "Node2",
+					Name:    "Test2",
+					Address: "127.0.0.1",
+					Port:    "81",
+					Labels:  nil,
+					Status:  api.HealthWarning,
+				},
+				{
+					ID:      "id3",
+					Node:    "Node3",
+					Name:    "Test3",
+					Address: "127.0.0.1",
+					Port:    "82",
+					Labels:  nil,
+					Status:  api.HealthCritical,
+				},
+			},
+			expected: &dynamic.Configuration{
+				TCP: &dynamic.TCPConfiguration{
+					Routers:           map[string]*dynamic.TCPRouter{},
+					Middlewares:       map[string]*dynamic.TCPMiddleware{},
+					Services:          map[string]*dynamic.TCPService{},
+					ServersTransports: map[string]*dynamic.TCPServersTransport{},
+				},
+				UDP: &dynamic.UDPConfiguration{
+					Routers:  map[string]*dynamic.UDPRouter{},
+					Services: map[string]*dynamic.UDPService{},
+				},
+				HTTP: &dynamic.HTTPConfiguration{
+					Routers: map[string]*dynamic.Router{
+						"Test1": {
+							Service:     "Test1",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+						"Test2": {
+							Service:     "Test2",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+					},
+					Middlewares: map[string]*dynamic.Middleware{},
+					Services: map[string]*dynamic.Service{
+						"Test1": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:80",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+						"Test2": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:81",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+					},
+					ServersTransports: map[string]*dynamic.ServersTransport{},
+				},
+			},
+		},
+		{
+			// The "any" health status allows for all status types, including ones not yet directly included in Consul
+			desc:         "test include 'any' health status",
+			strictChecks: []string{api.HealthAny},
+			items: []itemData{
+				{
+					ID:      "id",
+					Node:    "Node1",
+					Name:    "Test1",
+					Address: "127.0.0.1",
+					Port:    "80",
+					Labels:  nil,
+					Status:  api.HealthPassing,
+				},
+				{
+					ID:      "id2",
+					Node:    "Node2",
+					Name:    "Test2",
+					Address: "127.0.0.1",
+					Port:    "81",
+					Labels:  nil,
+					Status:  api.HealthWarning,
+				},
+				{
+					ID:      "id3",
+					Node:    "Node3",
+					Name:    "Test3",
+					Address: "127.0.0.1",
+					Port:    "82",
+					Labels:  nil,
+					Status:  api.HealthCritical,
+				},
+				{
+					ID:      "id4",
+					Node:    "Node4",
+					Name:    "Test4",
+					Address: "127.0.0.1",
+					Port:    "83",
+					Labels:  nil,
+					Status:  "some unsupported status",
+				},
+			},
+			expected: &dynamic.Configuration{
+				TCP: &dynamic.TCPConfiguration{
+					Routers:           map[string]*dynamic.TCPRouter{},
+					Middlewares:       map[string]*dynamic.TCPMiddleware{},
+					Services:          map[string]*dynamic.TCPService{},
+					ServersTransports: map[string]*dynamic.TCPServersTransport{},
+				},
+				UDP: &dynamic.UDPConfiguration{
+					Routers:  map[string]*dynamic.UDPRouter{},
+					Services: map[string]*dynamic.UDPService{},
+				},
+				HTTP: &dynamic.HTTPConfiguration{
+					Routers: map[string]*dynamic.Router{
+						"Test1": {
+							Service:     "Test1",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+						"Test2": {
+							Service:     "Test2",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+						"Test3": {
+							Service:     "Test3",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+						"Test4": {
+							Service:     "Test4",
+							Rule:        "Host(`foo.bar`)",
+							DefaultRule: true,
+						},
+					},
+					Middlewares: map[string]*dynamic.Middleware{},
+					Services: map[string]*dynamic.Service{
+						"Test1": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:80",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+						"Test2": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:81",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+						"Test3": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:82",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+						"Test4": {
+							LoadBalancer: &dynamic.ServersLoadBalancer{
+								Servers: []dynamic.Server{
+									{
+										URL: "http://127.0.0.1:83",
+									},
+								},
+								PassHostHeader: Bool(true),
+								ResponseForwarding: &dynamic.ResponseForwarding{
+									FlushInterval: ptypes.Duration(100 * time.Millisecond),
+								},
+							},
+						},
+					},
+					ServersTransports: map[string]*dynamic.ServersTransport{},
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			var config Configuration
+
+			config.SetDefaults()
+			config.DefaultRule = "Host(`foo.bar`)"
+
+			if test.strictChecks != nil {
+				config.StrictChecks = test.strictChecks
+			}
+
+			p := Provider{
+				Configuration: config,
+			}
+
+			err := p.Init()
+			require.NoError(t, err)
+
+			for i := 0; i < len(test.items); i++ {
+				var err error
+				test.items[i].ExtraConf, err = p.getExtraConf(test.items[i].Labels)
+				require.NoError(t, err)
+			}
+
+			configuration := p.buildConfiguration(context.Background(), test.items, nil)
+
+			assert.Equal(t, test.expected, configuration)
+		})
+	}
+}