API: expose runtime representation

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
mpl 2019-05-16 10:58:06 +02:00 committed by Traefiker Bot
parent 5cd9396dae
commit f6df556eb0
50 changed files with 2250 additions and 1158 deletions

8
Gopkg.lock generated
View file

@ -1489,13 +1489,6 @@
pruneopts = "NUT" pruneopts = "NUT"
revision = "c4434f09ec131ecf30f986d5dcb1636508bfa49a" revision = "c4434f09ec131ecf30f986d5dcb1636508bfa49a"
[[projects]]
digest = "1:84b9a5318d8ce3b8a9b1509bf15734f4f9dcd4decf9d9e9c7346a16c7b64d49e"
name = "github.com/thoas/stats"
packages = ["."]
pruneopts = "NUT"
revision = "4975baf6a358ed3ddaa42133996e1959f96c9300"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:99ce99ce6d6d0cbc5f822cda92095906e01d5546d60999ac839ab008938e4e17" digest = "1:99ce99ce6d6d0cbc5f822cda92095906e01d5546d60999ac839ab008938e4e17"
@ -2304,7 +2297,6 @@
"github.com/stretchr/testify/mock", "github.com/stretchr/testify/mock",
"github.com/stretchr/testify/require", "github.com/stretchr/testify/require",
"github.com/stvp/go-udp-testing", "github.com/stvp/go-udp-testing",
"github.com/thoas/stats",
"github.com/uber/jaeger-client-go", "github.com/uber/jaeger-client-go",
"github.com/uber/jaeger-client-go/config", "github.com/uber/jaeger-client-go/config",
"github.com/uber/jaeger-client-go/zipkin", "github.com/uber/jaeger-client-go/zipkin",

View file

@ -10,7 +10,7 @@ Let's see how.
### General ### General
This [documentation](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/). This [documentation](https://docs.traefik.io/) is built with [mkdocs](https://mkdocs.org/).
### Method 1: `Docker` and `make` ### Method 1: `Docker` and `make`

View file

@ -603,7 +603,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue)
func waitForTraefik(c *check.C, containerName string) { func waitForTraefik(c *check.C, containerName string) {
// Wait for Traefik to turn ready. // Wait for Traefik to turn ready.
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/providers/docker/routers", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName)) err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains(containerName))

View file

@ -333,7 +333,7 @@ func (s *AcmeSuite) TestNoValidLetsEncryptServer(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// Expected traefik works // Expected traefik works
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }

View file

@ -62,27 +62,23 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) _, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
resp, err := http.Get("http://127.0.0.1:8080/api/providers/docker/services") resp, err := http.Get("http://127.0.0.1:8080/api/rawdata")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer resp.Body.Close() defer resp.Body.Close()
var services []api.ServiceRepresentation var rtconf api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&services) err = json.NewDecoder(resp.Body).Decode(&rtconf)
c.Assert(err, checker.IsNil)
// check that we have only one service with n servers
c.Assert(services, checker.HasLen, 1)
c.Assert(services[0].ID, checker.Equals, composeService+"_integrationtest"+composeProject)
c.Assert(services[0].LoadBalancer.Servers, checker.HasLen, serviceCount)
resp, err = http.Get("http://127.0.0.1:8080/api/providers/docker/routers")
c.Assert(err, checker.IsNil)
defer resp.Body.Close()
var routers []api.RouterRepresentation
err = json.NewDecoder(resp.Body).Decode(&routers)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// check that we have only one router // check that we have only one router
c.Assert(routers, checker.HasLen, 1) c.Assert(rtconf.Routers, checker.HasLen, 1)
// check that we have only one service with n servers
services := rtconf.Services
c.Assert(services, checker.HasLen, 1)
for k, v := range services {
c.Assert(k, checker.Equals, "docker."+composeService+"_integrationtest"+composeProject)
c.Assert(v.LoadBalancer.Servers, checker.HasLen, serviceCount)
// We could break here, but we don't just to keep us honest.
}
} }

View file

@ -315,7 +315,7 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
c.Assert(json.Unmarshal(body, &version), checker.IsNil) c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0") c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/services", 60*time.Second, try.BodyContains("powpow")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
s.stopAndRemoveContainerByName(c, "powpow") s.stopAndRemoveContainerByName(c, "powpow")
@ -323,11 +323,11 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/services", 10*time.Second, try.BodyContains("powpow")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.NotNil) c.Assert(err, checker.NotNil)
s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla") s.startContainerWithNameAndLabels(c, "powpow", "swarm:1.0.0", labels, "manage", "token://blabla")
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/services", 60*time.Second, try.BodyContains("powpow")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }

View file

@ -1,34 +0,0 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[serversTransport]
rootCAs = [ """{{ .CertContent }}""" ]
[entryPoints]
[entryPoints.web-secure]
address = ":4443"
[api]
[providers]
[providers.file]
[http.routers]
[http.routers.router1]
rule = "Host(`127.0.0.1`)"
service = "service1"
[http.routers.router1.tls]
[http.services]
[http.services.service1.loadbalancer]
[[http.services.service1.loadbalancer.servers]]
url = "https://127.0.0.1:{{ .GRPCServerPort }}"
weight = 1
[tlsStores.default.DefaultCertificate]
certFile = """{{ .CertContent }}"""
keyFile = """{{ .KeyContent }}"""

View file

@ -167,7 +167,7 @@ func (s *GRPCSuite) TestGRPC(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var response string var response string
@ -205,7 +205,7 @@ func (s *GRPCSuite) TestGRPCh2c(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var response string var response string
@ -247,7 +247,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var response string var response string
@ -289,7 +289,7 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var response string var response string
@ -336,7 +336,7 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var client helloworld.Greeter_StreamExampleClient var client helloworld.Greeter_StreamExampleClient
client, closer, err := callStreamExampleClientGRPC() client, closer, err := callStreamExampleClientGRPC()
@ -364,7 +364,6 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
stopStreamExample := make(chan bool) stopStreamExample := make(chan bool)
lis, err := net.Listen("tcp", ":0") lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
_, port, err := net.SplitHostPort(lis.Addr().String()) _, port, err := net.SplitHostPort(lis.Addr().String())
@ -378,7 +377,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
}() }()
file := s.adaptFile(c, "fixtures/grpc/config_with_flush.toml", struct { file := s.adaptFile(c, "fixtures/grpc/config.toml", struct {
CertContent string CertContent string
KeyContent string KeyContent string
GRPCServerPort string GRPCServerPort string
@ -396,7 +395,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var client helloworld.Greeter_StreamExampleClient var client helloworld.Greeter_StreamExampleClient
@ -454,7 +453,7 @@ func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var response string var response string

View file

@ -41,7 +41,7 @@ func (s *HealthCheckSuite) TestSimpleConfiguration(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)
@ -117,7 +117,7 @@ func (s *HealthCheckSuite) doTestMultipleEntrypoints(c *check.C, fixture string)
defer cmd.Process.Kill() defer cmd.Process.Kill()
// Wait for traefik // Wait for traefik
err = try.GetRequest("http://localhost:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Host(`test.localhost`)")) err = try.GetRequest("http://localhost:8080/api/rawdata", 60*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Check entrypoint http1 // Check entrypoint http1
@ -194,7 +194,7 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 10*time.Second, try.BodyContains("Host(`test.localhost`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("Host(`test.localhost`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil) frontendHealthReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/health", nil)

View file

@ -32,7 +32,7 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -66,7 +66,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent)
@ -122,7 +122,7 @@ func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -146,7 +146,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -180,7 +180,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -214,7 +214,7 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -249,7 +249,7 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -282,7 +282,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -338,7 +338,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -399,7 +399,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -464,7 +464,7 @@ func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 1*time.Second, try.BodyContains(backend.URL)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(backend.URL))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8081/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8081/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -486,7 +486,7 @@ func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 1*time.Second, try.BodyContains(backend.URL)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(backend.URL))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8081/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8081/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -544,7 +544,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) {
} }
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent)
@ -613,7 +613,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
} }
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent)
@ -676,7 +676,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c
} }
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent)
@ -740,7 +740,7 @@ func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathModification(c *check.C)
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 5*time.Second, try.BodyContains("Host(`example.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.BodyContains("Host(`example.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
client := &http.Client{ client := &http.Client{
@ -841,7 +841,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.BodyContains("HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{

View file

@ -74,9 +74,9 @@ func (s *K8sSuite) TestCRDSimple(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("PathPrefix(`/tobestripped`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("PathPrefix(`/tobestripped`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/kubernetescrd/routers", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("default/stripprefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("default/stripprefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/kubernetescrd/middlewares", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("stripprefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("stripprefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }

View file

@ -11,6 +11,7 @@ server1:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.rt-server1.entryPoints=web - traefik.http.routers.rt-server1.entryPoints=web
- traefik.http.routers.rt-server1.rule=Host("frontend1.docker.local") - traefik.http.routers.rt-server1.rule=Host("frontend1.docker.local")
- traefik.http.routers.rt-server1.service=service1
- traefik.http.services.service1.loadbalancer.server.port=80 - traefik.http.services.service1.loadbalancer.server.port=80
server2: server2:
image: containous/whoami image: containous/whoami

View file

@ -65,7 +65,7 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) c.Assert(response.StatusCode, checker.Equals, http.StatusOK)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK))

View file

@ -31,7 +31,7 @@ func (s *RetrySuite) TestRetry(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// This simulates a DialTimeout when connecting to the backend server. // This simulates a DialTimeout when connecting to the backend server.
@ -53,7 +53,7 @@ func (s *RetrySuite) TestRetryWebsocket(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("PathPrefix(`/`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// This simulates a DialTimeout when connecting to the backend server. // This simulates a DialTimeout when connecting to the backend server.

View file

@ -60,7 +60,7 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
@ -173,10 +173,10 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8000/test", 1*time.Second, try.StatusCodeIs(http.StatusNotFound)) err = try.GetRequest("http://127.0.0.1:8000/test", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/api/providers/docker", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/api/rawdata", 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) err = try.GetRequest("http://127.0.0.1:8000/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -205,7 +205,7 @@ func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("PathPrefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -230,7 +230,7 @@ func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8001/api/providers", 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized)) err = try.GetRequest("http://127.0.0.1:8001/api/rawdata", 2*time.Second, try.StatusCodeIs(http.StatusUnauthorized))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8001/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8001/ping", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -248,7 +248,7 @@ func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -266,7 +266,7 @@ func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -284,7 +284,7 @@ func (s *SimpleSuite) TestMetricsPrometheusDefaultEntrypoint(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 1*time.Second, try.BodyContains("PathPrefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK))
@ -312,7 +312,7 @@ func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 1*time.Second, try.BodyContains("PathPrefix")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.BodyContains(ipWhoami01)) err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.BodyContains(ipWhoami01))
@ -334,10 +334,10 @@ func (s *SimpleSuite) TestIPStrategyWhitelist(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("override")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("override.remoteaddr.whitelist.docker.local")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("override.remoteaddr.whitelist.docker.local"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
testCases := []struct { testCases := []struct {
@ -415,7 +415,7 @@ func (s *SimpleSuite) TestXForwardedHeaders(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second,
try.BodyContains("override.remoteaddr.whitelist.docker.local")) try.BodyContains("override.remoteaddr.whitelist.docker.local"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
@ -450,7 +450,7 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 1000*time.Millisecond, try.BodyContains("service")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("service"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
config := config.HTTPConfiguration{ config := config.HTTPConfiguration{
@ -474,7 +474,7 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK) c.Assert(response.StatusCode, checker.Equals, http.StatusOK)
err = try.GetRequest("http://127.0.0.1:8080/api/providers/rest/routers", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("CustomValue")) err = try.GetRequest("http://127.0.0.1:8000/", 1*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("CustomValue"))

View file

@ -31,7 +31,7 @@ func (s *TCPSuite) TestMixed(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("Path(`/test`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("Path(`/test`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
//Traefik passes through, termination handled by whoami-a //Traefik passes through, termination handled by whoami-a

View file

@ -31,7 +31,7 @@ func (s *TimeoutSuite) TestForwardingTimeouts(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/routers", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("Path(`/dialTimeout`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// This simulates a DialTimeout when connecting to the backend server. // This simulates a DialTimeout when connecting to the backend server.

View file

@ -50,7 +50,7 @@ func (s *TLSClientHeadersSuite) TestTLSClientHeaders(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/providers/docker/routers", 2*time.Second, try.BodyContains("PathPrefix(`/`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 2*time.Second, try.BodyContains("PathPrefix(`/`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443", nil) request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443", nil)

View file

@ -59,6 +59,10 @@ func (s *TracingSuite) TestZipkinRateLimit(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest("http://127.0.0.1:8000/ratelimit", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
@ -106,6 +110,10 @@ func (s *TracingSuite) TestZipkinRetry(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway)) err = try.GetRequest("http://127.0.0.1:8000/retry", 500*time.Millisecond, try.StatusCodeIs(http.StatusBadGateway))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
@ -129,6 +137,10 @@ func (s *TracingSuite) TestZipkinAuth(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized)) err = try.GetRequest("http://127.0.0.1:8000/auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)

View file

@ -57,7 +57,7 @@ func (s *WebsocketSuite) TestBase(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil)
@ -107,7 +107,7 @@ func (s *WebsocketSuite) TestWrongOrigin(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:800") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:800")
@ -157,7 +157,7 @@ func (s *WebsocketSuite) TestOrigin(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000")
@ -218,7 +218,7 @@ func (s *WebsocketSuite) TestWrongOriginIgnoredByServer(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:80") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:80")
@ -276,7 +276,7 @@ func (s *WebsocketSuite) TestSSLTermination(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Add client self-signed cert // Add client self-signed cert
@ -339,7 +339,7 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000") config, err := websocket.NewConfig("ws://127.0.0.1:8000/ws", "ws://127.0.0.1:8000")
@ -383,7 +383,7 @@ func (s *WebsocketSuite) TestSpecificResponseFromBackend(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
_, resp, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) _, resp, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil)
@ -429,7 +429,7 @@ func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws/http%3A%2F%2Ftest", nil) conn, _, err := gorillawebsocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws/http%3A%2F%2Ftest", nil)
@ -484,7 +484,7 @@ func (s *WebsocketSuite) TestSSLhttp2(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
// Add client self-signed cert // Add client self-signed cert
@ -543,7 +543,7 @@ func (s *WebsocketSuite) TestHeaderAreForwared(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for traefik // wait for traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers/file/services", 10*time.Second, try.BodyContains("127.0.0.1")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
headers := http.Header{} headers := http.Header{}

View file

@ -6,79 +6,70 @@ import (
"github.com/containous/mux" "github.com/containous/mux"
"github.com/containous/traefik/pkg/config" "github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/log" "github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/types" "github.com/containous/traefik/pkg/types"
"github.com/containous/traefik/pkg/version" "github.com/containous/traefik/pkg/version"
assetfs "github.com/elazarl/go-bindata-assetfs" assetfs "github.com/elazarl/go-bindata-assetfs"
thoasstats "github.com/thoas/stats"
"github.com/unrolled/render" "github.com/unrolled/render"
) )
// ResourceIdentifier a resource identifier
type ResourceIdentifier struct {
ID string `json:"id"`
Path string `json:"path"`
}
// ProviderRepresentation a provider with resource identifiers
type ProviderRepresentation struct {
Routers []ResourceIdentifier `json:"routers,omitempty"`
Middlewares []ResourceIdentifier `json:"middlewares,omitempty"`
Services []ResourceIdentifier `json:"services,omitempty"`
}
// RouterRepresentation extended version of a router configuration with an ID
type RouterRepresentation struct {
*config.Router
ID string `json:"id"`
}
// MiddlewareRepresentation extended version of a middleware configuration with an ID
type MiddlewareRepresentation struct {
*config.Middleware
ID string `json:"id"`
}
// ServiceRepresentation extended version of a service configuration with an ID
type ServiceRepresentation struct {
*config.Service
ID string `json:"id"`
}
// Handler expose api routes
type Handler struct {
EntryPoint string
Dashboard bool
Debug bool
CurrentConfigurations *safe.Safe
Statistics *types.Statistics
Stats *thoasstats.Stats
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
DashboardAssets *assetfs.AssetFS
}
var templateRenderer jsonRenderer = render.New(render.Options{Directory: "nowhere"}) var templateRenderer jsonRenderer = render.New(render.Options{Directory: "nowhere"})
type jsonRenderer interface { type jsonRenderer interface {
JSON(w io.Writer, status int, v interface{}) error JSON(w io.Writer, status int, v interface{}) error
} }
type serviceInfoRepresentation struct {
*config.ServiceInfo
ServerStatus map[string]string `json:"serverStatus,omitempty"`
}
// RunTimeRepresentation is the configuration information exposed by the API handler.
type RunTimeRepresentation struct {
Routers map[string]*config.RouterInfo `json:"routers,omitempty"`
Middlewares map[string]*config.MiddlewareInfo `json:"middlewares,omitempty"`
Services map[string]*serviceInfoRepresentation `json:"services,omitempty"`
TCPRouters map[string]*config.TCPRouterInfo `json:"tcpRouters,omitempty"`
TCPServices map[string]*config.TCPServiceInfo `json:"tcpServices,omitempty"`
}
// Handler serves the configuration and status of Traefik on API endpoints.
type Handler struct {
dashboard bool
debug bool
// runtimeConfiguration is the data set used to create all the data representations exposed by the API.
runtimeConfiguration *config.RuntimeConfiguration
statistics *types.Statistics
// stats *thoasstats.Stats // FIXME stats
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
dashboardAssets *assetfs.AssetFS
}
// New returns a Handler defined by staticConfig, and if provided, by runtimeConfig.
// It finishes populating the information provided in the runtimeConfig.
func New(staticConfig static.Configuration, runtimeConfig *config.RuntimeConfiguration) *Handler {
rConfig := runtimeConfig
if rConfig == nil {
rConfig = &config.RuntimeConfiguration{}
}
return &Handler{
dashboard: staticConfig.API.Dashboard,
statistics: staticConfig.API.Statistics,
dashboardAssets: staticConfig.API.DashboardAssets,
runtimeConfiguration: rConfig,
debug: staticConfig.Global.Debug,
}
}
// Append add api routes on a router // Append add api routes on a router
func (h Handler) Append(router *mux.Router) { func (h Handler) Append(router *mux.Router) {
if h.Debug { if h.debug {
DebugHandler{}.Append(router) DebugHandler{}.Append(router)
} }
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRawData) router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
router.Methods(http.MethodGet).Path("/api/providers").HandlerFunc(h.getProvidersHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}").HandlerFunc(h.getProviderHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}/routers").HandlerFunc(h.getRoutersHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}/routers/{router}").HandlerFunc(h.getRouterHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}/middlewares").HandlerFunc(h.getMiddlewaresHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}/middlewares/{middleware}").HandlerFunc(h.getMiddlewareHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}/services").HandlerFunc(h.getServicesHandler)
router.Methods(http.MethodGet).Path("/api/providers/{provider}/services/{service}").HandlerFunc(h.getServiceHandler)
// FIXME stats // FIXME stats
// health route // health route
@ -86,268 +77,29 @@ func (h Handler) Append(router *mux.Router) {
version.Handler{}.Append(router) version.Handler{}.Append(router)
if h.Dashboard { if h.dashboard {
DashboardHandler{Assets: h.DashboardAssets}.Append(router) DashboardHandler{Assets: h.dashboardAssets}.Append(router)
} }
} }
func (h Handler) getRawData(rw http.ResponseWriter, request *http.Request) { func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
if h.CurrentConfigurations != nil { siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
currentConfigurations, ok := h.CurrentConfigurations.Get().(config.Configurations) for k, v := range h.runtimeConfiguration.Services {
if !ok { siRepr[k] = &serviceInfoRepresentation{
rw.WriteHeader(http.StatusOK) ServiceInfo: v,
return ServerStatus: v.GetAllStatus(),
}
err := templateRenderer.JSON(rw, http.StatusOK, currentConfigurations)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
} }
} }
}
func (h Handler) getProvidersHandler(rw http.ResponseWriter, request *http.Request) { rtRepr := RunTimeRepresentation{
// FIXME handle currentConfiguration Routers: h.runtimeConfiguration.Routers,
if h.CurrentConfigurations != nil { Middlewares: h.runtimeConfiguration.Middlewares,
currentConfigurations, ok := h.CurrentConfigurations.Get().(config.Configurations) Services: siRepr,
if !ok { TCPRouters: h.runtimeConfiguration.TCPRouters,
rw.WriteHeader(http.StatusOK) TCPServices: h.runtimeConfiguration.TCPServices,
return
}
var providers []ResourceIdentifier
for name := range currentConfigurations {
providers = append(providers, ResourceIdentifier{
ID: name,
Path: "/api/providers/" + name,
})
}
err := templateRenderer.JSON(rw, http.StatusOK, providers)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
}
func (h Handler) getProviderHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
} }
if provider.HTTP == nil { err := templateRenderer.JSON(rw, http.StatusOK, rtRepr)
http.NotFound(rw, request)
return
}
var routers []ResourceIdentifier
for name := range provider.HTTP.Routers {
routers = append(routers, ResourceIdentifier{
ID: name,
Path: "/api/providers/" + providerID + "/routers",
})
}
var services []ResourceIdentifier
for name := range provider.HTTP.Services {
services = append(services, ResourceIdentifier{
ID: name,
Path: "/api/providers/" + providerID + "/services",
})
}
var middlewares []ResourceIdentifier
for name := range provider.HTTP.Middlewares {
middlewares = append(middlewares, ResourceIdentifier{
ID: name,
Path: "/api/providers/" + providerID + "/middlewares",
})
}
providers := ProviderRepresentation{Routers: routers, Middlewares: middlewares, Services: services}
err := templateRenderer.JSON(rw, http.StatusOK, providers)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getRoutersHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
}
if provider.HTTP == nil {
http.NotFound(rw, request)
return
}
var routers []RouterRepresentation
for name, router := range provider.HTTP.Routers {
routers = append(routers, RouterRepresentation{Router: router, ID: name})
}
err := templateRenderer.JSON(rw, http.StatusOK, routers)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getRouterHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
routerID := mux.Vars(request)["router"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
}
if provider.HTTP == nil {
http.NotFound(rw, request)
return
}
router, ok := provider.HTTP.Routers[routerID]
if !ok {
http.NotFound(rw, request)
return
}
err := templateRenderer.JSON(rw, http.StatusOK, router)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getMiddlewaresHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
}
if provider.HTTP == nil {
http.NotFound(rw, request)
return
}
var middlewares []MiddlewareRepresentation
for name, middleware := range provider.HTTP.Middlewares {
middlewares = append(middlewares, MiddlewareRepresentation{Middleware: middleware, ID: name})
}
err := templateRenderer.JSON(rw, http.StatusOK, middlewares)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getMiddlewareHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
middlewareID := mux.Vars(request)["middleware"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
}
if provider.HTTP == nil {
http.NotFound(rw, request)
return
}
middleware, ok := provider.HTTP.Middlewares[middlewareID]
if !ok {
http.NotFound(rw, request)
return
}
err := templateRenderer.JSON(rw, http.StatusOK, middleware)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getServicesHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
}
if provider.HTTP == nil {
http.NotFound(rw, request)
return
}
var services []ServiceRepresentation
for name, service := range provider.HTTP.Services {
services = append(services, ServiceRepresentation{Service: service, ID: name})
}
err := templateRenderer.JSON(rw, http.StatusOK, services)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getServiceHandler(rw http.ResponseWriter, request *http.Request) {
providerID := mux.Vars(request)["provider"]
serviceID := mux.Vars(request)["service"]
currentConfigurations := h.CurrentConfigurations.Get().(config.Configurations)
provider, ok := currentConfigurations[providerID]
if !ok {
http.NotFound(rw, request)
return
}
if provider.HTTP == nil {
http.NotFound(rw, request)
return
}
service, ok := provider.HTTP.Services[serviceID]
if !ok {
http.NotFound(rw, request)
return
}
err := templateRenderer.JSON(rw, http.StatusOK, service)
if err != nil { if err != nil {
log.FromContext(request.Context()).Error(err) log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)

View file

@ -1,6 +1,8 @@
package api package api
import ( import (
"encoding/json"
"flag"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -8,188 +10,122 @@ import (
"github.com/containous/mux" "github.com/containous/mux"
"github.com/containous/traefik/pkg/config" "github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/safe" "github.com/containous/traefik/pkg/config/static"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
func TestHandler_Configuration(t *testing.T) { func TestHandler_Configuration(t *testing.T) {
type expected struct { type expected struct {
statusCode int statusCode int
body string json string
} }
testCases := []struct { testCases := []struct {
desc string desc string
path string path string
configuration config.Configurations conf config.RuntimeConfiguration
expected expected expected expected
}{ }{
{ {
desc: "Get all the providers", desc: "Get rawdata",
path: "/api/providers", path: "/api/rawdata",
configuration: config.Configurations{ conf: config.RuntimeConfiguration{
"foo": { Services: map[string]*config.ServiceInfo{
HTTP: &config.HTTPConfiguration{ "myprovider.foo-service": {
Routers: map[string]*config.Router{ Service: &config.Service{
"bar": {EntryPoints: []string{"foo", "bar"}}, LoadBalancer: &config.LoadBalancerService{
}, Servers: []config.Server{
}, {
}, URL: "http://127.0.0.1",
}, Weight: 1,
expected: expected{statusCode: http.StatusOK, body: `[{"id":"foo","path":"/api/providers/foo"}]`}, },
},
{
desc: "Get a provider",
path: "/api/providers/foo",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"bar": {EntryPoints: []string{"foo", "bar"}},
},
Middlewares: map[string]*config.Middleware{
"bar": {
AddPrefix: &config.AddPrefix{Prefix: "bar"},
},
},
Services: map[string]*config.Service{
"foo": {
LoadBalancer: &config.LoadBalancerService{
Method: "wrr",
}, },
Method: "wrr",
}, },
}, },
}, },
}, },
}, Middlewares: map[string]*config.MiddlewareInfo{
expected: expected{statusCode: http.StatusOK, body: `{"routers":[{"id":"bar","path":"/api/providers/foo/routers"}],"middlewares":[{"id":"bar","path":"/api/providers/foo/middlewares"}],"services":[{"id":"foo","path":"/api/providers/foo/services"}]}`}, "myprovider.auth": {
}, Middleware: &config.Middleware{
{ BasicAuth: &config.BasicAuth{
desc: "Provider not found", Users: []string{"admin:admin"},
path: "/api/providers/foo", },
configuration: config.Configurations{}, },
expected: expected{statusCode: http.StatusNotFound, body: "404 page not found\n"}, },
}, "myprovider.addPrefixTest": {
{ Middleware: &config.Middleware{
desc: "Get all routers", AddPrefix: &config.AddPrefix{
path: "/api/providers/foo/routers", Prefix: "/titi",
configuration: config.Configurations{ },
"foo": { },
HTTP: &config.HTTPConfiguration{ },
Routers: map[string]*config.Router{ "anotherprovider.addPrefixTest": {
"bar": {EntryPoints: []string{"foo", "bar"}}, Middleware: &config.Middleware{
AddPrefix: &config.AddPrefix{
Prefix: "/toto",
},
}, },
}, },
}, },
}, Routers: map[string]*config.RouterInfo{
expected: expected{statusCode: http.StatusOK, body: `[{"entryPoints":["foo","bar"],"id":"bar"}]`}, "myprovider.bar": {
}, Router: &config.Router{
{ EntryPoints: []string{"web"},
desc: "Get a router", Service: "myprovider.foo-service",
path: "/api/providers/foo/routers/bar", Rule: "Host(`foo.bar`)",
configuration: config.Configurations{ Middlewares: []string{"auth", "anotherprovider.addPrefixTest"},
"foo": { },
HTTP: &config.HTTPConfiguration{ },
Routers: map[string]*config.Router{ "myprovider.test": {
"bar": {EntryPoints: []string{"foo", "bar"}}, Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar.other`)",
Middlewares: []string{"addPrefixTest", "auth"},
}, },
}, },
}, },
}, TCPServices: map[string]*config.TCPServiceInfo{
expected: expected{statusCode: http.StatusOK, body: `{"entryPoints":["foo","bar"]}`}, "myprovider.tcpfoo-service": {
}, TCPService: &config.TCPService{
{ LoadBalancer: &config.TCPLoadBalancerService{
desc: "Router not found", Servers: []config.TCPServer{
path: "/api/providers/foo/routers/bar", {
configuration: config.Configurations{ Address: "127.0.0.1",
"foo": {}, Weight: 1,
}, },
expected: expected{statusCode: http.StatusNotFound, body: "404 page not found\n"},
},
{
desc: "Get all services",
path: "/api/providers/foo/services",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Services: map[string]*config.Service{
"foo": {
LoadBalancer: &config.LoadBalancerService{
Method: "wrr",
}, },
Method: "wrr",
}, },
}, },
}, },
}, },
}, TCPRouters: map[string]*config.TCPRouterInfo{
expected: expected{statusCode: http.StatusOK, body: `[{"loadbalancer":{"method":"wrr","passHostHeader":false},"id":"foo"}]`}, "myprovider.tcpbar": {
}, TCPRouter: &config.TCPRouter{
{ EntryPoints: []string{"web"},
desc: "Get a service", Service: "myprovider.tcpfoo-service",
path: "/api/providers/foo/services/foo", Rule: "HostSNI(`foo.bar`)",
configuration: config.Configurations{ },
"foo": { },
HTTP: &config.HTTPConfiguration{ "myprovider.tcptest": {
Services: map[string]*config.Service{ TCPRouter: &config.TCPRouter{
"foo": { EntryPoints: []string{"web"},
LoadBalancer: &config.LoadBalancerService{ Service: "myprovider.tcpfoo-service",
Method: "wrr", Rule: "HostSNI(`foo.bar.other`)",
},
},
}, },
}, },
}, },
}, },
expected: expected{statusCode: http.StatusOK, body: `{"loadbalancer":{"method":"wrr","passHostHeader":false}}`},
}, expected: expected{
{ statusCode: http.StatusOK,
desc: "Service not found", json: "testdata/getrawdata.json",
path: "/api/providers/foo/services/bar",
configuration: config.Configurations{
"foo": {},
}, },
expected: expected{statusCode: http.StatusNotFound, body: "404 page not found\n"},
},
{
desc: "Get all middlewares",
path: "/api/providers/foo/middlewares",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Middlewares: map[string]*config.Middleware{
"bar": {
AddPrefix: &config.AddPrefix{Prefix: "bar"},
},
},
},
},
},
expected: expected{statusCode: http.StatusOK, body: `[{"addPrefix":{"prefix":"bar"},"id":"bar"}]`},
},
{
desc: "Get a middleware",
path: "/api/providers/foo/middlewares/bar",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Middlewares: map[string]*config.Middleware{
"bar": {
AddPrefix: &config.AddPrefix{Prefix: "bar"},
},
},
},
},
},
expected: expected{statusCode: http.StatusOK, body: `{"addPrefix":{"prefix":"bar"}}`},
},
{
desc: "Middleware not found",
path: "/api/providers/foo/middlewares/bar",
configuration: config.Configurations{
"foo": {},
},
expected: expected{statusCode: http.StatusNotFound, body: "404 page not found\n"},
}, },
} }
@ -198,15 +134,11 @@ func TestHandler_Configuration(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
currentConfiguration := &safe.Safe{} rtConf := &test.conf
currentConfiguration.Set(test.configuration) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
handler := Handler{
CurrentConfigurations: currentConfiguration,
}
router := mux.NewRouter() router := mux.NewRouter()
handler.Append(router) handler.Append(router)
rtConf.PopulateUsedBy()
server := httptest.NewServer(router) server := httptest.NewServer(router)
@ -215,12 +147,30 @@ func TestHandler_Configuration(t *testing.T) {
assert.Equal(t, test.expected.statusCode, resp.StatusCode) assert.Equal(t, test.expected.statusCode, resp.StatusCode)
content, err := ioutil.ReadAll(resp.Body) contents, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err) require.NoError(t, err)
err = resp.Body.Close() err = resp.Body.Close()
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, test.expected.body, string(content)) if test.expected.json == "" {
return
}
if *updateExpected {
var rtRepr RunTimeRepresentation
err := json.Unmarshal(contents, &rtRepr)
require.NoError(t, err)
newJSON, err := json.MarshalIndent(rtRepr, "", "\t")
require.NoError(t, err)
err = ioutil.WriteFile(test.expected.json, newJSON, 0644)
require.NoError(t, err)
}
data, err := ioutil.ReadFile(test.expected.json)
require.NoError(t, err)
assert.JSONEq(t, string(data), string(contents))
}) })
} }
} }

106
pkg/api/testdata/getrawdata.json vendored Normal file
View file

@ -0,0 +1,106 @@
{
"routers": {
"myprovider.bar": {
"entryPoints": [
"web"
],
"middlewares": [
"auth",
"anotherprovider.addPrefixTest"
],
"service": "myprovider.foo-service",
"rule": "Host(`foo.bar`)"
},
"myprovider.test": {
"entryPoints": [
"web"
],
"middlewares": [
"addPrefixTest",
"auth"
],
"service": "myprovider.foo-service",
"rule": "Host(`foo.bar.other`)"
}
},
"middlewares": {
"anotherprovider.addPrefixTest": {
"addPrefix": {
"prefix": "/toto"
},
"usedBy": [
"myprovider.bar"
]
},
"myprovider.addPrefixTest": {
"addPrefix": {
"prefix": "/titi"
},
"usedBy": [
"myprovider.test"
]
},
"myprovider.auth": {
"basicAuth": {
"users": [
"admin:admin"
]
},
"usedBy": [
"myprovider.bar",
"myprovider.test"
]
}
},
"services": {
"myprovider.foo-service": {
"loadbalancer": {
"servers": [
{
"url": "http://127.0.0.1",
"weight": 1
}
],
"method": "wrr",
"passHostHeader": false
},
"usedBy": [
"myprovider.bar",
"myprovider.test"
]
}
},
"tcpRouters": {
"myprovider.tcpbar": {
"entryPoints": [
"web"
],
"service": "myprovider.tcpfoo-service",
"rule": "HostSNI(`foo.bar`)"
},
"myprovider.tcptest": {
"entryPoints": [
"web"
],
"service": "myprovider.tcpfoo-service",
"rule": "HostSNI(`foo.bar.other`)"
}
},
"tcpServices": {
"myprovider.tcpfoo-service": {
"loadbalancer": {
"servers": [
{
"address": "127.0.0.1",
"weight": 1
}
],
"method": "wrr"
},
"usedBy": [
"myprovider.tcpbar",
"myprovider.tcptest"
]
}
}
}

210
pkg/config/runtime.go Normal file
View file

@ -0,0 +1,210 @@
package config
import (
"sort"
"strings"
"sync"
"github.com/containous/traefik/pkg/log"
)
// RuntimeConfiguration holds the information about the currently running traefik instance.
type RuntimeConfiguration struct {
Routers map[string]*RouterInfo `json:"routers,omitempty"`
Middlewares map[string]*MiddlewareInfo `json:"middlewares,omitempty"`
Services map[string]*ServiceInfo `json:"services,omitempty"`
TCPRouters map[string]*TCPRouterInfo `json:"tcpRouters,omitempty"`
TCPServices map[string]*TCPServiceInfo `json:"tcpServices,omitempty"`
}
// NewRuntimeConfig returns a RuntimeConfiguration initialized with the given conf. It never returns nil.
func NewRuntimeConfig(conf Configuration) *RuntimeConfiguration {
if conf.HTTP == nil && conf.TCP == nil {
return &RuntimeConfiguration{}
}
runtimeConfig := &RuntimeConfiguration{}
if conf.HTTP != nil {
routers := conf.HTTP.Routers
if len(routers) > 0 {
runtimeConfig.Routers = make(map[string]*RouterInfo, len(routers))
for k, v := range routers {
runtimeConfig.Routers[k] = &RouterInfo{Router: v}
}
}
services := conf.HTTP.Services
if len(services) > 0 {
runtimeConfig.Services = make(map[string]*ServiceInfo, len(services))
for k, v := range services {
runtimeConfig.Services[k] = &ServiceInfo{Service: v}
}
}
middlewares := conf.HTTP.Middlewares
if len(middlewares) > 0 {
runtimeConfig.Middlewares = make(map[string]*MiddlewareInfo, len(middlewares))
for k, v := range middlewares {
runtimeConfig.Middlewares[k] = &MiddlewareInfo{Middleware: v}
}
}
}
if conf.TCP != nil {
if len(conf.TCP.Routers) > 0 {
runtimeConfig.TCPRouters = make(map[string]*TCPRouterInfo, len(conf.TCP.Routers))
for k, v := range conf.TCP.Routers {
runtimeConfig.TCPRouters[k] = &TCPRouterInfo{TCPRouter: v}
}
}
if len(conf.TCP.Services) > 0 {
runtimeConfig.TCPServices = make(map[string]*TCPServiceInfo, len(conf.TCP.Services))
for k, v := range conf.TCP.Services {
runtimeConfig.TCPServices[k] = &TCPServiceInfo{TCPService: v}
}
}
}
return runtimeConfig
}
// PopulateUsedBy populates all the UsedBy lists of the underlying fields of r,
// based on the relations between the included services, routers, and middlewares.
func (r *RuntimeConfiguration) PopulateUsedBy() {
if r == nil {
return
}
logger := log.WithoutContext()
for routerName, routerInfo := range r.Routers {
providerName := getProviderName(routerName)
if providerName == "" {
logger.WithField(log.RouterName, routerName).Error("router name is not fully qualified")
continue
}
for _, midName := range routerInfo.Router.Middlewares {
fullMidName := getQualifiedName(providerName, midName)
if _, ok := r.Middlewares[fullMidName]; !ok {
continue
}
r.Middlewares[fullMidName].UsedBy = append(r.Middlewares[fullMidName].UsedBy, routerName)
}
serviceName := getQualifiedName(providerName, routerInfo.Router.Service)
if _, ok := r.Services[serviceName]; !ok {
continue
}
r.Services[serviceName].UsedBy = append(r.Services[serviceName].UsedBy, routerName)
}
for k := range r.Services {
sort.Strings(r.Services[k].UsedBy)
}
for k := range r.Middlewares {
sort.Strings(r.Middlewares[k].UsedBy)
}
for routerName, routerInfo := range r.TCPRouters {
providerName := getProviderName(routerName)
if providerName == "" {
logger.WithField(log.RouterName, routerName).Error("tcp router name is not fully qualified")
continue
}
serviceName := getQualifiedName(providerName, routerInfo.TCPRouter.Service)
if _, ok := r.TCPServices[serviceName]; !ok {
continue
}
r.TCPServices[serviceName].UsedBy = append(r.TCPServices[serviceName].UsedBy, routerName)
}
for k := range r.TCPServices {
sort.Strings(r.TCPServices[k].UsedBy)
}
}
// RouterInfo holds information about a currently running HTTP router
type RouterInfo struct {
*Router // dynamic configuration
Err string `json:"error,omitempty"` // initialization error
}
// TCPRouterInfo holds information about a currently running TCP router
type TCPRouterInfo struct {
*TCPRouter // dynamic configuration
Err string `json:"error,omitempty"` // initialization error
}
// MiddlewareInfo holds information about a currently running middleware
type MiddlewareInfo struct {
*Middleware // dynamic configuration
Err error `json:"error,omitempty"` // initialization error
UsedBy []string `json:"usedBy,omitempty"` // list of routers and services using that middleware
}
// ServiceInfo holds information about a currently running service
type ServiceInfo struct {
*Service // dynamic configuration
Err error `json:"error,omitempty"` // initialization error
UsedBy []string `json:"usedBy,omitempty"` // list of routers using that service
statusMu sync.RWMutex
status map[string]string // keyed by server URL
}
// UpdateStatus sets the status of the server in the ServiceInfo.
// It is the responsibility of the caller to check that s is not nil.
func (s *ServiceInfo) UpdateStatus(server string, status string) {
s.statusMu.Lock()
defer s.statusMu.Unlock()
if s.status == nil {
s.status = make(map[string]string)
}
s.status[server] = status
}
// GetAllStatus returns all the statuses of all the servers in ServiceInfo.
// It is the responsibility of the caller to check that s is not nil
func (s *ServiceInfo) GetAllStatus() map[string]string {
s.statusMu.RLock()
defer s.statusMu.RUnlock()
if len(s.status) == 0 {
return nil
}
allStatus := make(map[string]string, len(s.status))
for k, v := range s.status {
allStatus[k] = v
}
return allStatus
}
// TCPServiceInfo holds information about a currently running TCP service
type TCPServiceInfo struct {
*TCPService // dynamic configuration
Err error `json:"error,omitempty"` // initialization error
UsedBy []string `json:"usedBy,omitempty"` // list of routers using that service
}
func getProviderName(elementName string) string {
parts := strings.Split(elementName, ".")
if len(parts) > 1 {
return parts[0]
}
return ""
}
func getQualifiedName(provider, elementName string) string {
parts := strings.Split(elementName, ".")
if len(parts) == 1 {
return provider + "." + elementName
}
return elementName
}

726
pkg/config/runtime_test.go Normal file
View file

@ -0,0 +1,726 @@
package config_test
import (
"testing"
"github.com/containous/traefik/pkg/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// all the Routers/Middlewares/Services are considered fully qualified
func TestPopulateUsedby(t *testing.T) {
testCases := []struct {
desc string
conf *config.RuntimeConfiguration
expected config.RuntimeConfiguration
}{
{
desc: "nil config",
conf: nil,
expected: config.RuntimeConfiguration{},
},
{
desc: "One service used by two routers",
conf: &config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.foo": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
},
},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:8085",
Weight: 1,
},
{
URL: "http://127.0.0.1:8086",
Weight: 1,
},
},
Method: "wrr",
HealthCheck: &config.HealthCheck{
Interval: "500ms",
Path: "/health",
},
},
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.foo": {},
"myprovider.bar": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar", "myprovider.foo"},
},
},
},
},
{
desc: "One service used by two routers, but one router with wrong rule",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.foo": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "WrongRule(`bar.foo`)",
},
},
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.foo": {},
"myprovider.bar": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar", "myprovider.foo"},
},
},
},
},
{
desc: "Broken Service used by one Router",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: nil,
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
{
desc: "2 different Services each used by a disctinct router.",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:8085",
Weight: 1,
},
{
URL: "http://127.0.0.1:8086",
Weight: 1,
},
},
Method: "wrr",
HealthCheck: &config.HealthCheck{
Interval: "500ms",
Path: "/health",
},
},
},
},
"myprovider.bar-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:8087",
Weight: 1,
},
{
URL: "http://127.0.0.1:8088",
Weight: 1,
},
},
Method: "wrr",
HealthCheck: &config.HealthCheck{
Interval: "500ms",
Path: "/health",
},
},
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.foo": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {},
"myprovider.foo": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.foo"},
},
"myprovider.bar-service": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
{
desc: "2 middlewares both used by 2 Routers",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
Middleware: &config.Middleware{
BasicAuth: &config.BasicAuth{
Users: []string{"admin:admin"},
},
},
},
"myprovider.addPrefixTest": {
Middleware: &config.Middleware{
AddPrefix: &config.AddPrefix{
Prefix: "/toto",
},
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"auth", "addPrefixTest"},
},
},
"myprovider.test": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar.other`)",
Middlewares: []string{"addPrefixTest", "auth"},
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {},
"myprovider.test": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar", "myprovider.test"},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
UsedBy: []string{"myprovider.bar", "myprovider.test"},
},
"myprovider.addPrefixTest": {
UsedBy: []string{"myprovider.bar", "myprovider.test"},
},
},
},
},
{
desc: "Unknown middleware is not used by the Router",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
Middleware: &config.Middleware{
BasicAuth: &config.BasicAuth{
Users: []string{"admin:admin"},
},
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"unknown"},
},
},
},
},
expected: config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
{
desc: "Broken middleware is used by Router",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
Middleware: &config.Middleware{
BasicAuth: &config.BasicAuth{
Users: []string{"badConf"},
},
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"myprovider.auth"},
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar"},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
{
desc: "2 middlewares from 2 disctinct providers both used by 2 Routers",
conf: &config.RuntimeConfiguration{
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
Middleware: &config.Middleware{
BasicAuth: &config.BasicAuth{
Users: []string{"admin:admin"},
},
},
},
"myprovider.addPrefixTest": {
Middleware: &config.Middleware{
AddPrefix: &config.AddPrefix{
Prefix: "/titi",
},
},
},
"anotherprovider.addPrefixTest": {
Middleware: &config.Middleware{
AddPrefix: &config.AddPrefix{
Prefix: "/toto",
},
},
},
},
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"auth", "anotherprovider.addPrefixTest"},
},
},
"myprovider.test": {
Router: &config.Router{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar.other`)",
Middlewares: []string{"addPrefixTest", "auth"},
},
},
},
},
expected: config.RuntimeConfiguration{
Routers: map[string]*config.RouterInfo{
"myprovider.bar": {},
"myprovider.test": {},
},
Services: map[string]*config.ServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar", "myprovider.test"},
},
},
Middlewares: map[string]*config.MiddlewareInfo{
"myprovider.auth": {
UsedBy: []string{"myprovider.bar", "myprovider.test"},
},
"myprovider.addPrefixTest": {
UsedBy: []string{"myprovider.test"},
},
"anotherprovider.addPrefixTest": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
// TCP tests from hereon
{
desc: "TCP, One service used by two routers",
conf: &config.RuntimeConfiguration{
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
"myprovider.bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
},
},
},
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1",
Port: "8085",
Weight: 1,
},
{
Address: "127.0.0.1",
Port: "8086",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
},
expected: config.RuntimeConfiguration{
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.foo": {},
"myprovider.bar": {},
},
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar", "myprovider.foo"},
},
},
},
},
{
desc: "TCP, One service used by two routers, but one router with wrong rule",
conf: &config.RuntimeConfiguration{
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "WrongRule(`bar.foo`)",
},
},
"myprovider.bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: config.RuntimeConfiguration{
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.foo": {},
"myprovider.bar": {},
},
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar", "myprovider.foo"},
},
},
},
},
{
desc: "TCP, Broken Service used by one Router",
conf: &config.RuntimeConfiguration{
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
TCPService: &config.TCPService{
LoadBalancer: nil,
},
},
},
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: config.RuntimeConfiguration{
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.bar": {},
},
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
{
desc: "TCP, 2 different Services each used by a disctinct router.",
conf: &config.RuntimeConfiguration{
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1",
Port: "8085",
Weight: 1,
},
{
Address: "127.0.0.1",
Port: "8086",
Weight: 1,
},
},
Method: "wrr",
},
},
},
"myprovider.bar-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1",
Port: "8087",
Weight: 1,
},
{
Address: "127.0.0.1",
Port: "8088",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.foo-service",
Rule: "Host(`bar.foo`)",
},
},
"myprovider.bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.bar-service",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: config.RuntimeConfiguration{
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.bar": {},
"myprovider.foo": {},
},
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.foo-service": {
UsedBy: []string{"myprovider.foo"},
},
"myprovider.bar-service": {
UsedBy: []string{"myprovider.bar"},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConf := test.conf
runtimeConf.PopulateUsedBy()
for key, expectedService := range test.expected.Services {
require.NotNil(t, runtimeConf.Services[key])
assert.Equal(t, expectedService.UsedBy, runtimeConf.Services[key].UsedBy)
}
for key, expectedMiddleware := range test.expected.Middlewares {
require.NotNil(t, runtimeConf.Middlewares[key])
assert.Equal(t, expectedMiddleware.UsedBy, runtimeConf.Middlewares[key].UsedBy)
}
for key, expectedTCPService := range test.expected.TCPServices {
require.NotNil(t, runtimeConf.TCPServices[key])
assert.Equal(t, expectedTCPService.UsedBy, runtimeConf.TCPServices[key].UsedBy)
}
})
}
}

View file

@ -10,12 +10,18 @@ import (
"sync" "sync"
"time" "time"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/log" "github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/safe" "github.com/containous/traefik/pkg/safe"
"github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics"
"github.com/vulcand/oxy/roundrobin" "github.com/vulcand/oxy/roundrobin"
) )
const (
serverUp = "UP"
serverDown = "DOWN"
)
var singleton *HealthCheck var singleton *HealthCheck
var once sync.Once var once sync.Once
@ -221,3 +227,38 @@ func checkHealth(serverURL *url.URL, backend *BackendConfig) error {
return nil return nil
} }
// NewLBStatusUpdater returns a new LbStatusUpdater
func NewLBStatusUpdater(bh BalancerHandler, svinfo *config.ServiceInfo) *LbStatusUpdater {
return &LbStatusUpdater{
BalancerHandler: bh,
serviceInfo: svinfo,
}
}
// LbStatusUpdater wraps a BalancerHandler and a ServiceInfo,
// so it can keep track of the status of a server in the ServiceInfo.
type LbStatusUpdater struct {
BalancerHandler
serviceInfo *config.ServiceInfo // can be nil
}
// RemoveServer removes the given server from the BalancerHandler,
// and updates the status of the server to "DOWN".
func (lb *LbStatusUpdater) RemoveServer(u *url.URL) error {
err := lb.BalancerHandler.RemoveServer(u)
if err == nil && lb.serviceInfo != nil {
lb.serviceInfo.UpdateStatus(u.String(), serverDown)
}
return err
}
// UpsertServer adds the given server to the BalancerHandler,
// and updates the status of the server to "UP".
func (lb *LbStatusUpdater) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
err := lb.BalancerHandler.UpsertServer(u, options...)
if err == nil && lb.serviceInfo != nil {
lb.serviceInfo.UpdateStatus(u.String(), serverUp)
}
return err
}

View file

@ -9,6 +9,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/testhelpers" "github.com/containous/traefik/pkg/testhelpers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -367,6 +368,8 @@ type testLoadBalancer struct {
numRemovedServers int numRemovedServers int
numUpsertedServers int numUpsertedServers int
servers []*url.URL servers []*url.URL
// options is just to make sure that LBStatusUpdater forwards options on Upsert to its BalancerHandler
options []roundrobin.ServerOption
} }
func (lb *testLoadBalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (lb *testLoadBalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -386,6 +389,7 @@ func (lb *testLoadBalancer) UpsertServer(u *url.URL, options ...roundrobin.Serve
defer lb.Unlock() defer lb.Unlock()
lb.numUpsertedServers++ lb.numUpsertedServers++
lb.servers = append(lb.servers, u) lb.servers = append(lb.servers, u)
lb.options = append(lb.options, options...)
return nil return nil
} }
@ -393,14 +397,23 @@ func (lb *testLoadBalancer) Servers() []*url.URL {
return lb.servers return lb.servers
} }
func (lb *testLoadBalancer) Options() []roundrobin.ServerOption {
return lb.options
}
func (lb *testLoadBalancer) removeServer(u *url.URL) { func (lb *testLoadBalancer) removeServer(u *url.URL) {
var i int var i int
var serverURL *url.URL var serverURL *url.URL
found := false
for i, serverURL = range lb.servers { for i, serverURL = range lb.servers {
if *serverURL == *u { if *serverURL == *u {
found = true
break break
} }
} }
if !found {
return
}
lb.servers = append(lb.servers[:i], lb.servers[i+1:]...) lb.servers = append(lb.servers[:i], lb.servers[i+1:]...)
} }
@ -427,3 +440,32 @@ func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
th.done() th.done()
} }
} }
func TestLBStatusUpdater(t *testing.T) {
lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}}
svInfo := &config.ServiceInfo{}
lbsu := NewLBStatusUpdater(lb, svInfo)
newServer, err := url.Parse("http://foo.com")
assert.Nil(t, err)
err = lbsu.UpsertServer(newServer, roundrobin.Weight(1))
assert.Nil(t, err)
assert.Equal(t, len(lbsu.Servers()), 1)
assert.Equal(t, len(lbsu.BalancerHandler.(*testLoadBalancer).Options()), 1)
statuses := svInfo.GetAllStatus()
assert.Equal(t, len(statuses), 1)
for k, v := range statuses {
assert.Equal(t, k, newServer.String())
assert.Equal(t, v, serverUp)
break
}
err = lbsu.RemoveServer(newServer)
assert.Nil(t, err)
assert.Equal(t, len(lbsu.Servers()), 0)
statuses = svInfo.GetAllStatus()
assert.Equal(t, len(statuses), 1)
for k, v := range statuses {
assert.Equal(t, k, newServer.String())
assert.Equal(t, v, serverDown)
break
}
}

View file

@ -8,13 +8,13 @@ import (
) )
// NewBuilder creates a builder. // NewBuilder creates a builder.
func NewBuilder(configs map[string]*config.Middleware) *Builder { func NewBuilder(configs map[string]*config.MiddlewareInfo) *Builder {
return &Builder{configs: configs} return &Builder{configs: configs}
} }
// Builder holds builder configuration. // Builder holds builder configuration.
type Builder struct { type Builder struct {
configs map[string]*config.Middleware configs map[string]*config.MiddlewareInfo
} }
// Build Builds the response modifier. // Build Builds the response modifier.

View file

@ -166,7 +166,12 @@ func TestBuilderBuild(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
builder := NewBuilder(test.conf) rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Middlewares: test.conf,
},
})
builder := NewBuilder(rtConf.Middlewares)
rm := builder.Build(context.Background(), test.middlewares) rm := builder.Build(context.Background(), test.middlewares)

View file

@ -39,7 +39,7 @@ const (
// Builder the middleware builder // Builder the middleware builder
type Builder struct { type Builder struct {
configs map[string]*config.Middleware configs map[string]*config.MiddlewareInfo
serviceBuilder serviceBuilder serviceBuilder serviceBuilder
} }
@ -48,7 +48,7 @@ type serviceBuilder interface {
} }
// NewBuilder creates a new Builder // NewBuilder creates a new Builder
func NewBuilder(configs map[string]*config.Middleware, serviceBuilder serviceBuilder) *Builder { func NewBuilder(configs map[string]*config.MiddlewareInfo, serviceBuilder serviceBuilder) *Builder {
return &Builder{configs: configs, serviceBuilder: serviceBuilder} return &Builder{configs: configs, serviceBuilder: serviceBuilder}
} }
@ -60,20 +60,29 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C
chain = chain.Append(func(next http.Handler) (http.Handler, error) { chain = chain.Append(func(next http.Handler) (http.Handler, error) {
constructorContext := internal.AddProviderInContext(ctx, middlewareName) constructorContext := internal.AddProviderInContext(ctx, middlewareName)
if _, ok := b.configs[middlewareName]; !ok { if midInf, ok := b.configs[middlewareName]; !ok || midInf.Middleware == nil {
return nil, fmt.Errorf("middleware %q does not exist", middlewareName) return nil, fmt.Errorf("middleware %q does not exist", middlewareName)
} }
var err error var err error
if constructorContext, err = checkRecursion(constructorContext, middlewareName); err != nil { if constructorContext, err = checkRecursion(constructorContext, middlewareName); err != nil {
b.configs[middlewareName].Err = err
return nil, err return nil, err
} }
constructor, err := b.buildConstructor(constructorContext, middlewareName, *b.configs[middlewareName]) constructor, err := b.buildConstructor(constructorContext, middlewareName)
if err != nil { if err != nil {
return nil, fmt.Errorf("error during instanciation of %s: %v", middlewareName, err) b.configs[middlewareName].Err = err
return nil, err
} }
return constructor(next)
handler, err := constructor(next)
if err != nil {
b.configs[middlewareName].Err = err
return nil, err
}
return handler, nil
}) })
} }
return &chain return &chain
@ -90,7 +99,9 @@ func checkRecursion(ctx context.Context, middlewareName string) (context.Context
return context.WithValue(ctx, middlewareStackKey, append(currentStack, middlewareName)), nil return context.WithValue(ctx, middlewareStackKey, append(currentStack, middlewareName)), nil
} }
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string, config config.Middleware) (alice.Constructor, error) { // it is the responsibility of the caller to make sure that b.configs[middlewareName].Middleware exists
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) {
config := b.configs[middlewareName]
var middleware alice.Constructor var middleware alice.Constructor
badConf := errors.New("cannot create middleware: multi-types middleware not supported, consider declaring two different pieces of middleware instead") badConf := errors.New("cannot create middleware: multi-types middleware not supported, consider declaring two different pieces of middleware instead")

View file

@ -14,7 +14,7 @@ import (
) )
func TestBuilder_BuildChainNilConfig(t *testing.T) { func TestBuilder_BuildChainNilConfig(t *testing.T) {
testConfig := map[string]*config.Middleware{ testConfig := map[string]*config.MiddlewareInfo{
"empty": {}, "empty": {},
} }
middlewaresBuilder := NewBuilder(testConfig, nil) middlewaresBuilder := NewBuilder(testConfig, nil)
@ -25,7 +25,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) {
} }
func TestBuilder_BuildChainNonExistentChain(t *testing.T) { func TestBuilder_BuildChainNonExistentChain(t *testing.T) {
testConfig := map[string]*config.Middleware{ testConfig := map[string]*config.MiddlewareInfo{
"foobar": {}, "foobar": {},
} }
middlewaresBuilder := NewBuilder(testConfig, nil) middlewaresBuilder := NewBuilder(testConfig, nil)
@ -264,7 +264,12 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
ctx = internal.AddProviderInContext(ctx, test.contextProvider+".foobar") ctx = internal.AddProviderInContext(ctx, test.contextProvider+".foobar")
} }
builder := NewBuilder(test.configuration, nil) rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Middlewares: test.configuration,
},
})
builder := NewBuilder(rtConf.Middlewares, nil)
result := builder.BuildChain(ctx, test.buildChain) result := builder.BuildChain(ctx, test.buildChain)
@ -310,7 +315,12 @@ func TestBuilder_buildConstructor(t *testing.T) {
}, },
} }
middlewaresBuilder := NewBuilder(testConfig, nil) rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Middlewares: testConfig,
},
})
middlewaresBuilder := NewBuilder(rtConf.Middlewares, nil)
testCases := []struct { testCases := []struct {
desc string desc string
@ -344,7 +354,8 @@ func TestBuilder_buildConstructor(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
constructor, err := middlewaresBuilder.buildConstructor(context.Background(), test.middlewareID, *testConfig[test.middlewareID]) constructor, err := middlewaresBuilder.buildConstructor(context.Background(), test.middlewareID)
require.NoError(t, err) require.NoError(t, err)
middleware, err2 := constructor(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) middleware, err2 := constructor(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))

View file

@ -6,10 +6,10 @@ import (
"github.com/containous/alice" "github.com/containous/alice"
"github.com/containous/mux" "github.com/containous/mux"
"github.com/containous/traefik/pkg/api" "github.com/containous/traefik/pkg/api"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static" "github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/log" "github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/metrics" "github.com/containous/traefik/pkg/metrics"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/types" "github.com/containous/traefik/pkg/types"
) )
@ -19,7 +19,8 @@ type chainBuilder interface {
} }
// NewRouteAppenderAggregator Creates a new RouteAppenderAggregator // NewRouteAppenderAggregator Creates a new RouteAppenderAggregator
func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder, conf static.Configuration, entryPointName string, currentConfiguration *safe.Safe) *RouteAppenderAggregator { func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder, conf static.Configuration,
entryPointName string, runtimeConfiguration *config.RuntimeConfiguration) *RouteAppenderAggregator {
aggregator := &RouteAppenderAggregator{} aggregator := &RouteAppenderAggregator{}
if conf.Providers != nil && conf.Providers.Rest != nil { if conf.Providers != nil && conf.Providers.Rest != nil {
@ -29,17 +30,9 @@ func NewRouteAppenderAggregator(ctx context.Context, chainBuilder chainBuilder,
if conf.API != nil && conf.API.EntryPoint == entryPointName { if conf.API != nil && conf.API.EntryPoint == entryPointName {
chain := chainBuilder.BuildChain(ctx, conf.API.Middlewares) chain := chainBuilder.BuildChain(ctx, conf.API.Middlewares)
aggregator.AddAppender(&WithMiddleware{ aggregator.AddAppender(&WithMiddleware{
appender: api.Handler{ appender: api.New(conf, runtimeConfiguration),
EntryPoint: conf.API.EntryPoint,
Dashboard: conf.API.Dashboard,
Statistics: conf.API.Statistics,
DashboardAssets: conf.API.DashboardAssets,
CurrentConfigurations: currentConfiguration,
Debug: conf.Global.Debug,
},
routerMiddlewares: chain, routerMiddlewares: chain,
}) })
} }
if conf.Ping != nil && conf.Ping.EntryPoint == entryPointName { if conf.Ping != nil && conf.Ping.EntryPoint == entryPointName {

View file

@ -62,7 +62,7 @@ func TestNewRouteAppenderAggregator(t *testing.T) {
"/wrong": http.StatusBadGateway, "/wrong": http.StatusBadGateway,
"/ping": http.StatusOK, "/ping": http.StatusOK,
// "/.well-known/acme-challenge/token": http.StatusNotFound, // FIXME // "/.well-known/acme-challenge/token": http.StatusNotFound, // FIXME
"/api/providers": http.StatusUnauthorized, "/api/rawdata": http.StatusUnauthorized,
}, },
}, },
{ {

View file

@ -3,9 +3,9 @@ package router
import ( import (
"context" "context"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static" "github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/provider/acme" "github.com/containous/traefik/pkg/provider/acme"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/server/middleware" "github.com/containous/traefik/pkg/server/middleware"
"github.com/containous/traefik/pkg/types" "github.com/containous/traefik/pkg/types"
) )
@ -27,8 +27,8 @@ type RouteAppenderFactory struct {
} }
// NewAppender Creates a new RouteAppender // NewAppender Creates a new RouteAppender
func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, currentConfiguration *safe.Safe) types.RouteAppender { func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, runtimeConfiguration *config.RuntimeConfiguration) types.RouteAppender {
aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, currentConfiguration) aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, runtimeConfiguration)
if r.acmeProvider != nil && r.acmeProvider.HTTPChallenge != nil && r.acmeProvider.HTTPChallenge.EntryPoint == r.entryPointName { if r.acmeProvider != nil && r.acmeProvider.HTTPChallenge != nil && r.acmeProvider.HTTPChallenge.EntryPoint == r.entryPointName {
aggregator.AddAppender(r.acmeProvider) aggregator.AddAppender(r.acmeProvider)

View file

@ -23,7 +23,7 @@ const (
) )
// NewManager Creates a new Manager // NewManager Creates a new Manager
func NewManager(routers map[string]*config.Router, func NewManager(routers map[string]*config.RouterInfo,
serviceManager *service.Manager, middlewaresBuilder *middleware.Builder, modifierBuilder *responsemodifiers.Builder, serviceManager *service.Manager, middlewaresBuilder *middleware.Builder, modifierBuilder *responsemodifiers.Builder,
) *Manager { ) *Manager {
return &Manager{ return &Manager{
@ -38,7 +38,7 @@ func NewManager(routers map[string]*config.Router,
// Manager A route/router manager // Manager A route/router manager
type Manager struct { type Manager struct {
routerHandlers map[string]http.Handler routerHandlers map[string]http.Handler
configs map[string]*config.Router configs map[string]*config.RouterInfo
serviceManager *service.Manager serviceManager *service.Manager
middlewaresBuilder *middleware.Builder middlewaresBuilder *middleware.Builder
modifierBuilder *responsemodifiers.Builder modifierBuilder *responsemodifiers.Builder
@ -75,8 +75,17 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
return entryPointHandlers return entryPointHandlers
} }
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.Router { func contains(entryPoints []string, entryPointName string) bool {
entryPointsRouters := make(map[string]map[string]*config.Router) for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.RouterInfo {
entryPointsRouters := make(map[string]map[string]*config.RouterInfo)
for rtName, rt := range m.configs { for rtName, rt := range m.configs {
if (tls && rt.TLS == nil) || (!tls && rt.TLS != nil) { if (tls && rt.TLS == nil) || (!tls && rt.TLS != nil) {
@ -95,7 +104,7 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls
} }
if _, ok := entryPointsRouters[entryPointName]; !ok { if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*config.Router) entryPointsRouters[entryPointName] = make(map[string]*config.RouterInfo)
} }
entryPointsRouters[entryPointName][rtName] = rt entryPointsRouters[entryPointName][rtName] = rt
@ -105,7 +114,7 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls
return entryPointsRouters return entryPointsRouters
} }
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.Router) (http.Handler, error) { func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.RouterInfo) (http.Handler, error) {
router, err := rules.NewRouter() router, err := rules.NewRouter()
if err != nil { if err != nil {
return nil, err return nil, err
@ -117,12 +126,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
handler, err := m.buildRouterHandler(ctxRouter, routerName) handler, err := m.buildRouterHandler(ctxRouter, routerName)
if err != nil { if err != nil {
routerConfig.Err = err.Error()
logger.Error(err) logger.Error(err)
continue continue
} }
err = router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler) err = router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
if err != nil { if err != nil {
routerConfig.Err = err.Error()
logger.Error(err) logger.Error(err)
continue continue
} }
@ -166,7 +177,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string) (ht
return m.routerHandlers[routerName], nil return m.routerHandlers[routerName], nil
} }
func (m *Manager) buildHTTPHandler(ctx context.Context, router *config.Router, routerName string) (http.Handler, error) { func (m *Manager) buildHTTPHandler(ctx context.Context, router *config.RouterInfo, routerName string) (http.Handler, error) {
qualifiedNames := make([]string, len(router.Middlewares)) qualifiedNames := make([]string, len(router.Middlewares))
for i, name := range router.Middlewares { for i, name := range router.Middlewares {
qualifiedNames[i] = internal.GetQualifiedName(ctx, name) qualifiedNames[i] = internal.GetQualifiedName(ctx, name)
@ -186,12 +197,3 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *config.Router, r
return alice.New().Extend(*mHandler).Append(tHandler).Then(sHandler) return alice.New().Extend(*mHandler).Append(tHandler).Then(sHandler)
} }
func contains(entryPoints []string, entryPointName string) bool {
for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}

View file

@ -314,11 +314,17 @@ func TestRouterManager_Get(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
serviceManager := service.NewManager(test.serviceConfig, http.DefaultTransport) rtConf := config.NewRuntimeConfig(config.Configuration{
middlewaresBuilder := middleware.NewBuilder(test.middlewaresConfig, serviceManager) HTTP: &config.HTTPConfiguration{
responseModifierFactory := responsemodifiers.NewBuilder(test.middlewaresConfig) Services: test.serviceConfig,
Routers: test.routersConfig,
routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory) Middlewares: test.middlewaresConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -413,11 +419,17 @@ func TestAccessLog(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
serviceManager := service.NewManager(test.serviceConfig, http.DefaultTransport) rtConf := config.NewRuntimeConfig(config.Configuration{
middlewaresBuilder := middleware.NewBuilder(test.middlewaresConfig, serviceManager) HTTP: &config.HTTPConfiguration{
responseModifierFactory := responsemodifiers.NewBuilder(test.middlewaresConfig) Services: test.serviceConfig,
Routers: test.routersConfig,
routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory) Middlewares: test.middlewaresConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -443,6 +455,310 @@ func TestAccessLog(t *testing.T) {
} }
} }
func TestRuntimeConfiguration(t *testing.T) {
testCases := []struct {
desc string
serviceConfig map[string]*config.Service
routerConfig map[string]*config.Router
middlewareConfig map[string]*config.Middleware
expectedError int
}{
{
desc: "No error",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1:8085",
Weight: 1,
},
{
URL: "http://127.0.0.1:8086",
Weight: 1,
},
},
Method: "wrr",
HealthCheck: &config.HealthCheck{
Interval: "500ms",
Path: "/health",
},
},
},
},
routerConfig: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
},
},
expectedError: 0,
},
{
desc: "One router with wrong rule",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
routerConfig: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "WrongRule(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
},
},
expectedError: 1,
},
{
desc: "All router with wrong rule",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
routerConfig: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "WrongRule(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "WrongRule(`foo.bar`)",
},
},
expectedError: 2,
},
{
desc: "Router with unknown service",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
routerConfig: map[string]*config.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "wrong-service",
Rule: "Host(`bar.foo`)",
},
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
},
},
expectedError: 1,
},
{
desc: "Router with broken service",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: nil,
},
},
routerConfig: map[string]*config.Router{
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
},
},
expectedError: 2,
},
{
desc: "Router with middleware",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
middlewareConfig: map[string]*config.Middleware{
"auth": {
BasicAuth: &config.BasicAuth{
Users: []string{"admin:admin"},
},
},
"addPrefixTest": {
AddPrefix: &config.AddPrefix{
Prefix: "/toto",
},
},
},
routerConfig: map[string]*config.Router{
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"auth", "addPrefixTest"},
},
"test": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar.other`)",
Middlewares: []string{"addPrefixTest", "auth"},
},
},
},
{
desc: "Router with unknown middleware",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
middlewareConfig: map[string]*config.Middleware{
"auth": {
BasicAuth: &config.BasicAuth{
Users: []string{"admin:admin"},
},
},
},
routerConfig: map[string]*config.Router{
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"unknown"},
},
},
expectedError: 1,
},
{
desc: "Router with broken middleware",
serviceConfig: map[string]*config.Service{
"foo-service": {
LoadBalancer: &config.LoadBalancerService{
Servers: []config.Server{
{
URL: "http://127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
middlewareConfig: map[string]*config.Middleware{
"auth": {
BasicAuth: &config.BasicAuth{
Users: []string{"foo"},
},
},
},
routerConfig: map[string]*config.Router{
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"auth"},
},
},
expectedError: 2,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
entryPoints := []string{"web"}
rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Services: test.serviceConfig,
Routers: test.routerConfig,
Middlewares: test.middlewareConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*config.MiddlewareInfo{})
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
// even though rtConf was passed by argument to the manager builders above,
// it's ok to use it as the result we check, because everything worth checking
// can be accessed by pointers in it.
var allErrors int
for _, v := range rtConf.Services {
if v.Err != nil {
allErrors++
}
}
for _, v := range rtConf.Routers {
if v.Err != "" {
allErrors++
}
}
for _, v := range rtConf.Middlewares {
if v.Err != nil {
allErrors++
}
}
assert.Equal(t, test.expectedError, allErrors)
})
}
}
type staticTransport struct { type staticTransport struct {
res *http.Response res *http.Response
} }
@ -480,11 +796,17 @@ func BenchmarkRouterServe(b *testing.B) {
} }
entryPoints := []string{"web"} entryPoints := []string{"web"}
serviceManager := service.NewManager(serviceConfig, &staticTransport{res}) rtConf := config.NewRuntimeConfig(config.Configuration{
middlewaresBuilder := middleware.NewBuilder(map[string]*config.Middleware{}, serviceManager) HTTP: &config.HTTPConfiguration{
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*config.Middleware{}) Services: serviceConfig,
Routers: routersConfig,
routerManager := NewManager(routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory) Middlewares: map[string]*config.Middleware{},
},
})
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res})
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -498,6 +820,7 @@ func BenchmarkRouterServe(b *testing.B) {
} }
} }
func BenchmarkService(b *testing.B) { func BenchmarkService(b *testing.B) {
res := &http.Response{ res := &http.Response{
StatusCode: 200, StatusCode: 200,
@ -518,7 +841,12 @@ func BenchmarkService(b *testing.B) {
}, },
} }
serviceManager := service.NewManager(serviceConfig, &staticTransport{res}) rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Services: serviceConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res})
w := httptest.NewRecorder() w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)

View file

@ -3,6 +3,7 @@ package tcp
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt"
"net/http" "net/http"
"github.com/containous/traefik/pkg/config" "github.com/containous/traefik/pkg/config"
@ -14,14 +15,14 @@ import (
) )
// NewManager Creates a new Manager // NewManager Creates a new Manager
func NewManager(routers map[string]*config.TCPRouter, func NewManager(conf *config.RuntimeConfiguration,
serviceManager *tcpservice.Manager, serviceManager *tcpservice.Manager,
httpHandlers map[string]http.Handler, httpHandlers map[string]http.Handler,
httpsHandlers map[string]http.Handler, httpsHandlers map[string]http.Handler,
tlsConfig *tls.Config, tlsConfig *tls.Config,
) *Manager { ) *Manager {
return &Manager{ return &Manager{
configs: routers, configs: conf.TCPRouters,
serviceManager: serviceManager, serviceManager: serviceManager,
httpHandlers: httpHandlers, httpHandlers: httpHandlers,
httpsHandlers: httpsHandlers, httpsHandlers: httpsHandlers,
@ -31,7 +32,7 @@ func NewManager(routers map[string]*config.TCPRouter,
// Manager is a route/router manager // Manager is a route/router manager
type Manager struct { type Manager struct {
configs map[string]*config.TCPRouter configs map[string]*config.TCPRouterInfo
serviceManager *tcpservice.Manager serviceManager *tcpservice.Manager
httpHandlers map[string]http.Handler httpHandlers map[string]http.Handler
httpsHandlers map[string]http.Handler httpsHandlers map[string]http.Handler
@ -60,7 +61,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m
return entryPointHandlers return entryPointHandlers
} }
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouter, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) { func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) {
router := &tcp.Router{} router := &tcp.Router{}
router.HTTPHandler(handlerHTTP) router.HTTPHandler(handlerHTTP)
router.HTTPSHandler(handlerHTTPS, m.tlsConfig) router.HTTPSHandler(handlerHTTPS, m.tlsConfig)
@ -71,18 +72,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
handler, err := m.serviceManager.BuildTCP(ctxRouter, routerConfig.Service) handler, err := m.serviceManager.BuildTCP(ctxRouter, routerConfig.Service)
if err != nil { if err != nil {
routerConfig.Err = err.Error()
logger.Error(err) logger.Error(err)
continue continue
} }
domains, err := rules.ParseHostSNI(routerConfig.Rule) domains, err := rules.ParseHostSNI(routerConfig.Rule)
if err != nil { if err != nil {
logger.Debugf("Unknown rule %s", routerConfig.Rule) routerErr := fmt.Errorf("unknown rule %s", routerConfig.Rule)
routerConfig.Err = routerErr.Error()
logger.Debug(routerErr)
continue continue
} }
for _, domain := range domains { for _, domain := range domains {
logger.Debugf("Add route %s on TCP", domain) logger.Debugf("Adding route %s on TCP", domain)
switch { switch {
case routerConfig.TLS != nil: case routerConfig.TLS != nil:
if routerConfig.TLS.Passthrough { if routerConfig.TLS.Passthrough {
@ -101,8 +105,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return router, nil return router, nil
} }
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.TCPRouter { func contains(entryPoints []string, entryPointName string) bool {
entryPointsRouters := make(map[string]map[string]*config.TCPRouter) for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.TCPRouterInfo {
entryPointsRouters := make(map[string]map[string]*config.TCPRouterInfo)
for rtName, rt := range m.configs { for rtName, rt := range m.configs {
eps := rt.EntryPoints eps := rt.EntryPoints
@ -118,7 +131,7 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map
} }
if _, ok := entryPointsRouters[entryPointName]; !ok { if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*config.TCPRouter) entryPointsRouters[entryPointName] = make(map[string]*config.TCPRouterInfo)
} }
entryPointsRouters[entryPointName][rtName] = rt entryPointsRouters[entryPointName][rtName] = rt
@ -127,12 +140,3 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map
return entryPointsRouters return entryPointsRouters
} }
func contains(entryPoints []string, entryPointName string) bool {
for _, name := range entryPoints {
if name == entryPointName {
return true
}
}
return false
}

View file

@ -0,0 +1,223 @@
package tcp
import (
"context"
"testing"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/server/service/tcp"
"github.com/stretchr/testify/assert"
)
func TestRuntimeConfiguration(t *testing.T) {
testCases := []struct {
desc string
serviceConfig map[string]*config.TCPServiceInfo
routerConfig map[string]*config.TCPRouterInfo
expectedError int
}{
{
desc: "No error",
serviceConfig: map[string]*config.TCPServiceInfo{
"foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Port: "8085",
Address: "127.0.0.1:8085",
},
{
Address: "127.0.0.1:8086",
Port: "8086",
},
},
Method: "wrr",
},
},
},
},
routerConfig: map[string]*config.TCPRouterInfo{
"foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`bar.foo`)",
},
},
"bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`foo.bar`)",
},
},
},
expectedError: 0,
},
{
desc: "One router with wrong rule",
serviceConfig: map[string]*config.TCPServiceInfo{
"foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1:80",
},
},
Method: "wrr",
},
},
},
},
routerConfig: map[string]*config.TCPRouterInfo{
"foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "WrongRule(`bar.foo`)",
},
},
"bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`foo.bar`)",
},
},
},
expectedError: 1,
},
{
desc: "All router with wrong rule",
serviceConfig: map[string]*config.TCPServiceInfo{
"foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
routerConfig: map[string]*config.TCPRouterInfo{
"foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "WrongRule(`bar.foo`)",
},
},
"bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "WrongRule(`foo.bar`)",
},
},
},
expectedError: 2,
},
{
desc: "Router with unknown service",
serviceConfig: map[string]*config.TCPServiceInfo{
"foo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1:80",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
routerConfig: map[string]*config.TCPRouterInfo{
"foo": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "wrong-service",
Rule: "HostSNI(`bar.foo`)",
},
},
"bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`foo.bar`)",
},
},
},
expectedError: 1,
},
{
desc: "Router with broken service",
serviceConfig: map[string]*config.TCPServiceInfo{
"foo-service": {
TCPService: &config.TCPService{
LoadBalancer: nil,
},
},
},
routerConfig: map[string]*config.TCPRouterInfo{
"bar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "HostSNI(`foo.bar`)",
},
},
},
expectedError: 2,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
entryPoints := []string{"web"}
conf := &config.RuntimeConfiguration{
TCPServices: test.serviceConfig,
TCPRouters: test.routerConfig,
}
serviceManager := tcp.NewManager(conf)
routerManager := NewManager(conf, serviceManager,
nil, nil, nil)
_ = routerManager.BuildHandlers(context.Background(), entryPoints)
// even though conf was passed by argument to the manager builders above,
// it's ok to use it as the result we check, because everything worth checking
// can be accessed by pointers in it.
var allErrors int
for _, v := range conf.TCPServices {
if v.Err != nil {
allErrors++
}
}
for _, v := range conf.TCPRouters {
if v.Err != "" {
allErrors++
}
}
assert.Equal(t, test.expectedError, allErrors)
})
}
}

View file

@ -50,7 +50,7 @@ type Server struct {
// RouteAppenderFactory the route appender factory interface // RouteAppenderFactory the route appender factory interface
type RouteAppenderFactory interface { type RouteAppenderFactory interface {
NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, currentConfigurations *safe.Safe) types.RouteAppender NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, runtimeConfiguration *config.RuntimeConfiguration) types.RouteAppender
} }
func setupTracing(conf *static.Tracing) tracing.TrackingBackend { func setupTracing(conf *static.Tracing) tracing.TrackingBackend {

View file

@ -69,47 +69,46 @@ func (s *Server) loadConfigurationTCP(configurations config.Configurations) map[
s.tlsManager.UpdateConfigs(conf.TLSStores, conf.TLSOptions, conf.TLS) s.tlsManager.UpdateConfigs(conf.TLSStores, conf.TLSOptions, conf.TLS)
handlersNonTLS, handlersTLS := s.createHTTPHandlers(ctx, *conf.HTTP, entryPoints) rtConf := config.NewRuntimeConfig(conf)
handlersNonTLS, handlersTLS := s.createHTTPHandlers(ctx, rtConf, entryPoints)
routersTCP := s.createTCPRouters(ctx, conf.TCP, entryPoints, handlersNonTLS, handlersTLS, s.tlsManager.Get("default", "default")) routersTCP := s.createTCPRouters(ctx, rtConf, entryPoints, handlersNonTLS, handlersTLS, s.tlsManager.Get("default", "default"))
rtConf.PopulateUsedBy()
return routersTCP return routersTCP
} }
func (s *Server) createTCPRouters(ctx context.Context, configuration *config.TCPConfiguration, entryPoints []string, handlers map[string]http.Handler, handlersTLS map[string]http.Handler, tlsConfig *tls.Config) map[string]*tcpCore.Router { // the given configuration must not be nil. its fields will get mutated.
func (s *Server) createTCPRouters(ctx context.Context, configuration *config.RuntimeConfiguration, entryPoints []string, handlers map[string]http.Handler, handlersTLS map[string]http.Handler, tlsConfig *tls.Config) map[string]*tcpCore.Router {
if configuration == nil { if configuration == nil {
return make(map[string]*tcpCore.Router) return make(map[string]*tcpCore.Router)
} }
serviceManager := tcp.NewManager(configuration.Services) serviceManager := tcp.NewManager(configuration)
routerManager := routertcp.NewManager(configuration.Routers, serviceManager, handlers, handlersTLS, tlsConfig) routerManager := routertcp.NewManager(configuration, serviceManager, handlers, handlersTLS, tlsConfig)
return routerManager.BuildHandlers(ctx, entryPoints) return routerManager.BuildHandlers(ctx, entryPoints)
} }
func (s *Server) createHTTPHandlers(ctx context.Context, configuration config.HTTPConfiguration, entryPoints []string) (map[string]http.Handler, map[string]http.Handler) { // createHTTPHandlers returns, for the given configuration and entryPoints, the HTTP handlers for non-TLS connections, and for the TLS ones. the given configuration must not be nil. its fields will get mutated.
func (s *Server) createHTTPHandlers(ctx context.Context, configuration *config.RuntimeConfiguration, entryPoints []string) (map[string]http.Handler, map[string]http.Handler) {
serviceManager := service.NewManager(configuration.Services, s.defaultRoundTripper) serviceManager := service.NewManager(configuration.Services, s.defaultRoundTripper)
middlewaresBuilder := middleware.NewBuilder(configuration.Middlewares, serviceManager) middlewaresBuilder := middleware.NewBuilder(configuration.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(configuration.Middlewares) responseModifierFactory := responsemodifiers.NewBuilder(configuration.Middlewares)
routerManager := router.NewManager(configuration.Routers, serviceManager, middlewaresBuilder, responseModifierFactory) routerManager := router.NewManager(configuration.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
handlersNonTLS := routerManager.BuildHandlers(ctx, entryPoints, false) handlersNonTLS := routerManager.BuildHandlers(ctx, entryPoints, false)
handlersTLS := routerManager.BuildHandlers(ctx, entryPoints, true) handlersTLS := routerManager.BuildHandlers(ctx, entryPoints, true)
routerHandlers := make(map[string]http.Handler) routerHandlers := make(map[string]http.Handler)
for _, entryPointName := range entryPoints { for _, entryPointName := range entryPoints {
internalMuxRouter := mux.NewRouter(). internalMuxRouter := mux.NewRouter().SkipClean(true)
SkipClean(true)
ctx = log.With(ctx, log.Str(log.EntryPointName, entryPointName)) ctx = log.With(ctx, log.Str(log.EntryPointName, entryPointName))
factory := s.entryPointsTCP[entryPointName].RouteAppenderFactory factory := s.entryPointsTCP[entryPointName].RouteAppenderFactory
if factory != nil { if factory != nil {
// FIXME remove currentConfigurations // FIXME remove currentConfigurations
appender := factory.NewAppender(ctx, middlewaresBuilder, &s.currentConfigurations) appender := factory.NewAppender(ctx, middlewaresBuilder, configuration)
appender.Append(internalMuxRouter) appender.Append(internalMuxRouter)
} }

View file

@ -47,7 +47,8 @@ func TestReuseService(t *testing.T) {
srv := NewServer(staticConfig, nil, entryPoints, nil) srv := NewServer(staticConfig, nil, entryPoints, nil)
entrypointsHandlers, _ := srv.createHTTPHandlers(context.Background(), *dynamicConfigs, []string{"http"}) rtConf := config.NewRuntimeConfig(config.Configuration{HTTP: dynamicConfigs})
entrypointsHandlers, _ := srv.createHTTPHandlers(context.Background(), rtConf, []string{"http"})
// Test that the /ok path returns a status 200. // Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{} responseRecorderOk := &httptest.ResponseRecorder{}

View file

@ -258,7 +258,8 @@ func TestServerResponseEmptyBackend(t *testing.T) {
} }
srv := NewServer(globalConfig, nil, entryPointsConfig, nil) srv := NewServer(globalConfig, nil, entryPointsConfig, nil)
entryPoints, _ := srv.createHTTPHandlers(context.Background(), *test.config(testServer.URL), []string{"http"}) rtConf := config.NewRuntimeConfig(config.Configuration{HTTP: test.config(testServer.URL)})
entryPoints, _ := srv.createHTTPHandlers(context.Background(), rtConf, []string{"http"})
responseRecorder := &httptest.ResponseRecorder{} responseRecorder := &httptest.ResponseRecorder{}
request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil) request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil)

View file

@ -26,7 +26,7 @@ const (
) )
// NewManager creates a new Manager // NewManager creates a new Manager
func NewManager(configs map[string]*config.Service, defaultRoundTripper http.RoundTripper) *Manager { func NewManager(configs map[string]*config.ServiceInfo, defaultRoundTripper http.RoundTripper) *Manager {
return &Manager{ return &Manager{
bufferPool: newBufferPool(), bufferPool: newBufferPool(),
defaultRoundTripper: defaultRoundTripper, defaultRoundTripper: defaultRoundTripper,
@ -40,7 +40,7 @@ type Manager struct {
bufferPool httputil.BufferPool bufferPool httputil.BufferPool
defaultRoundTripper http.RoundTripper defaultRoundTripper http.RoundTripper
balancers map[string][]healthcheck.BalancerHandler balancers map[string][]healthcheck.BalancerHandler
configs map[string]*config.Service configs map[string]*config.ServiceInfo
} }
// BuildHTTP Creates a http.Handler for a service configuration. // BuildHTTP Creates a http.Handler for a service configuration.
@ -50,15 +50,25 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
serviceName = internal.GetQualifiedName(ctx, serviceName) serviceName = internal.GetQualifiedName(ctx, serviceName)
ctx = internal.AddProviderInContext(ctx, serviceName) ctx = internal.AddProviderInContext(ctx, serviceName)
if conf, ok := m.configs[serviceName]; ok { conf, ok := m.configs[serviceName]
// TODO Should handle multiple service types if !ok {
// FIXME Check if the service is declared multiple times with different types return nil, fmt.Errorf("the service %q does not exist", serviceName)
if conf.LoadBalancer != nil {
return m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier)
}
return nil, fmt.Errorf("the service %q doesn't have any load balancer", serviceName)
} }
return nil, fmt.Errorf("the service %q does not exits", serviceName)
// TODO Should handle multiple service types
// FIXME Check if the service is declared multiple times with different types
if conf.LoadBalancer == nil {
conf.Err = fmt.Errorf("the service %q doesn't have any load balancer", serviceName)
return nil, conf.Err
}
lb, err := m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier)
if err != nil {
conf.Err = err
return nil, err
}
return lb, nil
} }
func (m *Manager) getLoadBalancerServiceHandler( func (m *Manager) getLoadBalancerServiceHandler(
@ -158,7 +168,7 @@ func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler
} }
if timeout >= interval { if timeout >= interval {
logger.Warnf("Health check timeout for backend '%s' should be lower than the health check interval. Interval set to timeout + 1 second (%s).", backend) logger.Warnf("Health check timeout for backend '%s' should be lower than the health check interval. Interval set to timeout + 1 second (%s).", backend, interval)
} }
return &healthcheck.Options{ return &healthcheck.Options{
@ -229,7 +239,8 @@ func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, servi
} }
} }
if err := m.upsertServers(ctx, lb, service.Servers); err != nil { lbsu := healthcheck.NewLBStatusUpdater(lb, m.configs[serviceName])
if err := m.upsertServers(ctx, lbsu, service.Servers); err != nil {
return nil, fmt.Errorf("error configuring load balancer for service %s: %v", serviceName, err) return nil, fmt.Errorf("error configuring load balancer for service %s: %v", serviceName, err)
} }

View file

@ -275,33 +275,39 @@ func TestManager_Build(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
serviceName string serviceName string
configs map[string]*config.Service configs map[string]*config.ServiceInfo
providerName string providerName string
}{ }{
{ {
desc: "Simple service name", desc: "Simple service name",
serviceName: "serviceName", serviceName: "serviceName",
configs: map[string]*config.Service{ configs: map[string]*config.ServiceInfo{
"serviceName": { "serviceName": {
LoadBalancer: &config.LoadBalancerService{Method: "wrr"}, Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{Method: "wrr"},
},
}, },
}, },
}, },
{ {
desc: "Service name with provider", desc: "Service name with provider",
serviceName: "provider-1.serviceName", serviceName: "provider-1.serviceName",
configs: map[string]*config.Service{ configs: map[string]*config.ServiceInfo{
"provider-1.serviceName": { "provider-1.serviceName": {
LoadBalancer: &config.LoadBalancerService{Method: "wrr"}, Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{Method: "wrr"},
},
}, },
}, },
}, },
{ {
desc: "Service name with provider in context", desc: "Service name with provider in context",
serviceName: "serviceName", serviceName: "serviceName",
configs: map[string]*config.Service{ configs: map[string]*config.ServiceInfo{
"provider-1.serviceName": { "provider-1.serviceName": {
LoadBalancer: &config.LoadBalancerService{Method: "wrr"}, Service: &config.Service{
LoadBalancer: &config.LoadBalancerService{Method: "wrr"},
},
}, },
}, },
providerName: "provider-1", providerName: "provider-1",

View file

@ -13,13 +13,13 @@ import (
// Manager is the TCPHandlers factory // Manager is the TCPHandlers factory
type Manager struct { type Manager struct {
configs map[string]*config.TCPService configs map[string]*config.TCPServiceInfo
} }
// NewManager creates a new manager // NewManager creates a new manager
func NewManager(configs map[string]*config.TCPService) *Manager { func NewManager(conf *config.RuntimeConfiguration) *Manager {
return &Manager{ return &Manager{
configs: configs, configs: conf.TCPServices,
} }
} }
@ -29,23 +29,23 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
ctx := internal.AddProviderInContext(rootCtx, serviceQualifiedName) ctx := internal.AddProviderInContext(rootCtx, serviceQualifiedName)
ctx = log.With(ctx, log.Str(log.ServiceName, serviceName)) ctx = log.With(ctx, log.Str(log.ServiceName, serviceName))
// FIXME Check if the service is declared multiple times with different types
conf, ok := m.configs[serviceQualifiedName] conf, ok := m.configs[serviceQualifiedName]
if !ok { if !ok {
return nil, fmt.Errorf("the service %q does not exits", serviceQualifiedName) return nil, fmt.Errorf("the service %q does not exist", serviceQualifiedName)
} }
if conf.LoadBalancer == nil { if conf.LoadBalancer == nil {
return nil, fmt.Errorf("the service %q doesn't have any TCP load balancer", serviceQualifiedName) conf.Err = fmt.Errorf("the service %q doesn't have any TCP load balancer", serviceQualifiedName)
return nil, conf.Err
} }
logger := log.FromContext(ctx) logger := log.FromContext(ctx)
// FIXME Check if the service is declared multiple times with different types
loadBalancer := tcp.NewRRLoadBalancer() loadBalancer := tcp.NewRRLoadBalancer()
for _, server := range conf.LoadBalancer.Servers { for _, server := range conf.LoadBalancer.Servers {
if _, err := parseIP(server.Address); err != nil { if _, _, err := net.SplitHostPort(server.Address); err != nil {
logger.Errorf("Invalid IP address for a %q server %q: %v", serviceQualifiedName, server.Address, err) logger.Errorf("In service %q: %v", serviceQualifiedName, err)
continue continue
} }
@ -57,20 +57,5 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
loadBalancer.AddServer(handler) loadBalancer.AddServer(handler)
} }
return loadBalancer, nil return loadBalancer, nil
} }
func parseIP(s string) (string, error) {
ip, _, err := net.SplitHostPort(s)
if err == nil {
return ip, nil
}
ipNoPort := net.ParseIP(s)
if ipNoPort == nil {
return "", fmt.Errorf("invalid IP Address %s", ipNoPort)
}
return ipNoPort.String(), nil
}

View file

@ -5,6 +5,8 @@ import (
"testing" "testing"
"github.com/containous/traefik/pkg/config" "github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/server/internal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -12,49 +14,166 @@ func TestManager_BuildTCP(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
serviceName string serviceName string
configs map[string]*config.TCPService configs map[string]*config.TCPServiceInfo
providerName string
expectedError string expectedError string
}{ }{
{ {
desc: "without configuration", desc: "without configuration",
serviceName: "test", serviceName: "test",
configs: nil, configs: nil,
expectedError: `the service "test" does not exits`, expectedError: `the service "test" does not exist`,
}, },
{ {
desc: "missing lb configuration", desc: "missing lb configuration",
serviceName: "test", serviceName: "test",
configs: map[string]*config.TCPService{ configs: map[string]*config.TCPServiceInfo{
"test": {}, "test": {
TCPService: &config.TCPService{},
},
}, },
expectedError: `the service "test" doesn't have any TCP load balancer`, expectedError: `the service "test" doesn't have any TCP load balancer`,
}, },
{ {
desc: "no such host", desc: "no such host, server is skipped, error is logged",
serviceName: "test", serviceName: "test",
configs: map[string]*config.TCPService{ configs: map[string]*config.TCPServiceInfo{
"test": { "test": {
LoadBalancer: &config.TCPLoadBalancerService{ TCPService: &config.TCPService{
Servers: []config.TCPServer{ LoadBalancer: &config.TCPLoadBalancerService{
{Address: "test:31"}, Servers: []config.TCPServer{
{Address: "test:31"},
},
}, },
}, },
}, },
}, },
}, },
{ {
desc: "invalid IP address", desc: "invalid IP address, server is skipped, error is logged",
serviceName: "test", serviceName: "test",
configs: map[string]*config.TCPService{ configs: map[string]*config.TCPServiceInfo{
"test": { "test": {
LoadBalancer: &config.TCPLoadBalancerService{ TCPService: &config.TCPService{
Servers: []config.TCPServer{ LoadBalancer: &config.TCPLoadBalancerService{
{Address: "foobar"}, Servers: []config.TCPServer{
{Address: "foobar"},
},
}, },
}, },
}, },
}, },
}, },
{
desc: "Simple service name",
serviceName: "serviceName",
configs: map[string]*config.TCPServiceInfo{
"serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{Method: "wrr"},
},
},
},
},
{
desc: "Service name with provider",
serviceName: "provider-1.serviceName",
configs: map[string]*config.TCPServiceInfo{
"provider-1.serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{Method: "wrr"},
},
},
},
},
{
desc: "Service name with provider in context",
serviceName: "serviceName",
configs: map[string]*config.TCPServiceInfo{
"provider-1.serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{Method: "wrr"},
},
},
},
providerName: "provider-1",
},
{
desc: "Server with correct host:port as address",
serviceName: "serviceName",
configs: map[string]*config.TCPServiceInfo{
"provider-1.serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "foobar.com:80",
},
},
Method: "wrr",
},
},
},
},
providerName: "provider-1",
},
{
desc: "Server with correct ip:port as address",
serviceName: "serviceName",
configs: map[string]*config.TCPServiceInfo{
"provider-1.serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "192.168.0.12:80",
},
},
Method: "wrr",
},
},
},
},
providerName: "provider-1",
},
{
desc: "missing port in address with hostname, server is skipped, error is logged",
serviceName: "serviceName",
configs: map[string]*config.TCPServiceInfo{
"provider-1.serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "foobar.com",
},
},
Method: "wrr",
},
},
},
},
providerName: "provider-1",
},
{
desc: "missing port in address with ip, server is skipped, error is logged",
serviceName: "serviceName",
configs: map[string]*config.TCPServiceInfo{
"provider-1.serviceName": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "192.168.0.12",
},
},
Method: "wrr",
},
},
},
},
providerName: "provider-1",
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -62,19 +181,22 @@ func TestManager_BuildTCP(t *testing.T) {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()
manager := NewManager(test.configs) manager := NewManager(&config.RuntimeConfiguration{
TCPServices: test.configs,
})
handler, err := manager.BuildTCP(context.Background(), test.serviceName) ctx := context.Background()
if len(test.providerName) > 0 {
ctx = internal.AddProviderInContext(ctx, test.providerName+".foobar")
}
handler, err := manager.BuildTCP(ctx, test.serviceName)
if test.expectedError != "" { if test.expectedError != "" {
if err == nil { assert.EqualError(t, err, test.expectedError)
require.Error(t, err) require.Nil(t, handler)
} else {
require.EqualError(t, err, test.expectedError)
require.Nil(t, handler)
}
} else { } else {
require.NoError(t, err) assert.Nil(t, err)
require.NotNil(t, handler) require.NotNil(t, handler)
} }
}) })

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Florent Messa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,59 +0,0 @@
package stats
// Options are stats options.
type Options struct {
statusCode *int
size int
recorder ResponseWriter
}
// StatusCode returns the response status code.
func (o Options) StatusCode() int {
if o.recorder != nil {
return o.recorder.Status()
}
return *o.statusCode
}
// Size returns the response size.
func (o Options) Size() int {
if o.recorder != nil {
return o.recorder.Size()
}
return o.size
}
// Option represents a stats option.
type Option func(*Options)
// WithStatusCode sets the status code to use in stats.
func WithStatusCode(statusCode int) Option {
return func(o *Options) {
o.statusCode = &statusCode
}
}
// WithSize sets the size to use in stats.
func WithSize(size int) Option {
return func(o *Options) {
o.size = size
}
}
// WithRecorder sets the recorder to use in stats.
func WithRecorder(recorder ResponseWriter) Option {
return func(o *Options) {
o.recorder = recorder
}
}
// newOptions takes functional options and returns options.
func newOptions(options ...Option) *Options {
opts := &Options{}
for _, o := range options {
o(opts)
}
return opts
}

View file

@ -1,95 +0,0 @@
package stats
import (
"bufio"
"fmt"
"net"
"net/http"
)
type ResponseWriter interface {
http.ResponseWriter
http.Flusher
// Status returns the status code of the response or 0 if the response has not been written.
Status() int
// Written returns whether or not the ResponseWriter has been written.
Written() bool
// Size returns the size of the response body.
Size() int
// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
Before(func(ResponseWriter))
}
type beforeFunc func(ResponseWriter)
type recorderResponseWriter struct {
http.ResponseWriter
status int
size int
beforeFuncs []beforeFunc
written bool
}
func NewRecorderResponseWriter(w http.ResponseWriter, statusCode int) ResponseWriter {
return &recorderResponseWriter{ResponseWriter: w, status: statusCode}
}
func (r *recorderResponseWriter) WriteHeader(code int) {
r.written = true
r.ResponseWriter.WriteHeader(code)
r.status = code
}
func (r *recorderResponseWriter) Flush() {
flusher, ok := r.ResponseWriter.(http.Flusher)
if ok {
flusher.Flush()
}
}
func (r *recorderResponseWriter) Status() int {
return r.status
}
func (r *recorderResponseWriter) Write(b []byte) (int, error) {
if !r.Written() {
// The status will be StatusOK if WriteHeader has not been called yet
r.WriteHeader(http.StatusOK)
}
size, err := r.ResponseWriter.Write(b)
r.size += size
return size, err
}
// Proxy method to Status to add support for gocraft
func (r *recorderResponseWriter) StatusCode() int {
return r.Status()
}
func (r *recorderResponseWriter) Size() int {
return r.size
}
func (r *recorderResponseWriter) Written() bool {
return r.StatusCode() != 0
}
func (r *recorderResponseWriter) CloseNotify() <-chan bool {
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
func (r *recorderResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if !r.written {
r.status = 0
}
hijacker, ok := r.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
}
return hijacker.Hijack()
}
func (r *recorderResponseWriter) Before(before func(ResponseWriter)) {
r.beforeFuncs = append(r.beforeFuncs, before)
}

View file

@ -1,227 +0,0 @@
package stats
import (
"fmt"
"net/http"
"os"
"sync"
"time"
)
// Stats data structure
type Stats struct {
mu sync.RWMutex
closed chan struct{}
Hostname string
Uptime time.Time
Pid int
ResponseCounts map[string]int
TotalResponseCounts map[string]int
TotalResponseTime time.Time
TotalResponseSize int64
MetricsCounts map[string]int
MetricsTimers map[string]time.Time
}
// Label data structure
type Label struct {
Name string
Value string
}
// New constructs a new Stats structure
func New() *Stats {
name, _ := os.Hostname()
stats := &Stats{
closed: make(chan struct{}, 1),
Uptime: time.Now(),
Pid: os.Getpid(),
ResponseCounts: map[string]int{},
TotalResponseCounts: map[string]int{},
TotalResponseTime: time.Time{},
Hostname: name,
}
go func() {
for {
select {
case <-stats.closed:
return
default:
stats.ResetResponseCounts()
time.Sleep(time.Second * 1)
}
}
}()
return stats
}
func (mw *Stats) Close() {
close(mw.closed)
}
// ResetResponseCounts reset the response counts
func (mw *Stats) ResetResponseCounts() {
mw.mu.Lock()
defer mw.mu.Unlock()
mw.ResponseCounts = map[string]int{}
}
// Handler is a MiddlewareFunc makes Stats implement the Middleware interface.
func (mw *Stats) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
beginning, recorder := mw.Begin(w)
h.ServeHTTP(recorder, r)
mw.End(beginning, WithRecorder(recorder))
})
}
// Negroni compatible interface
func (mw *Stats) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
beginning, recorder := mw.Begin(w)
next(recorder, r)
mw.End(beginning, WithRecorder(recorder))
}
// Begin starts a recorder
func (mw *Stats) Begin(w http.ResponseWriter) (time.Time, ResponseWriter) {
start := time.Now()
writer := NewRecorderResponseWriter(w, 200)
return start, writer
}
// End closes the recorder with a specific status
func (mw *Stats) End(start time.Time, opts ...Option) {
options := newOptions(opts...)
responseTime := time.Since(start)
mw.mu.Lock()
defer mw.mu.Unlock()
// If Hijacked connection do not count in response time
if options.StatusCode() != 0 {
statusCode := fmt.Sprintf("%d", options.StatusCode())
mw.ResponseCounts[statusCode]++
mw.TotalResponseCounts[statusCode]++
mw.TotalResponseTime = mw.TotalResponseTime.Add(responseTime)
mw.TotalResponseSize += int64(options.Size())
}
}
// MeasureSince method for execution time recording
func (mw *Stats) MeasureSince(key string, start time.Time) {
mw.MeasureSinceWithLabels(key, start, nil)
}
// MeasureSinceWithLabels method for execution time recording with custom labels
func (mw *Stats) MeasureSinceWithLabels(key string, start time.Time, labels []Label) {
labels = append(labels, Label{"host", mw.Hostname})
elapsed := time.Since(start)
mw.mu.Lock()
defer mw.mu.Unlock()
mw.MetricsCounts[key]++
mw.MetricsTimers[key] = mw.MetricsTimers[key].Add(elapsed)
}
// Data serializable structure
type Data struct {
Pid int `json:"pid"`
Hostname string `json:"hostname"`
UpTime string `json:"uptime"`
UpTimeSec float64 `json:"uptime_sec"`
Time string `json:"time"`
TimeUnix int64 `json:"unixtime"`
StatusCodeCount map[string]int `json:"status_code_count"`
TotalStatusCodeCount map[string]int `json:"total_status_code_count"`
Count int `json:"count"`
TotalCount int `json:"total_count"`
TotalResponseTime string `json:"total_response_time"`
TotalResponseTimeSec float64 `json:"total_response_time_sec"`
TotalResponseSize int64 `json:"total_response_size"`
AverageResponseSize int64 `json:"average_response_size"`
AverageResponseTime string `json:"average_response_time"`
AverageResponseTimeSec float64 `json:"average_response_time_sec"`
TotalMetricsCounts map[string]int `json:"total_metrics_counts"`
AverageMetricsTimers map[string]float64 `json:"average_metrics_timers"`
}
// Data returns the data serializable structure
func (mw *Stats) Data() *Data {
mw.mu.RLock()
responseCounts := make(map[string]int, len(mw.ResponseCounts))
totalResponseCounts := make(map[string]int, len(mw.TotalResponseCounts))
totalMetricsCounts := make(map[string]int, len(mw.MetricsCounts))
metricsCounts := make(map[string]float64, len(mw.MetricsCounts))
now := time.Now()
uptime := now.Sub(mw.Uptime)
count := 0
for code, current := range mw.ResponseCounts {
responseCounts[code] = current
count += current
}
totalCount := 0
for code, count := range mw.TotalResponseCounts {
totalResponseCounts[code] = count
totalCount += count
}
totalResponseTime := mw.TotalResponseTime.Sub(time.Time{})
totalResponseSize := mw.TotalResponseSize
averageResponseTime := time.Duration(0)
averageResponseSize := int64(0)
if totalCount > 0 {
avgNs := int64(totalResponseTime) / int64(totalCount)
averageResponseTime = time.Duration(avgNs)
averageResponseSize = int64(totalResponseSize) / int64(totalCount)
}
for key, count := range mw.MetricsCounts {
totalMetric := mw.MetricsTimers[key].Sub(time.Time{})
avgNs := int64(totalMetric) / int64(count)
metricsCounts[key] = time.Duration(avgNs).Seconds()
totalMetricsCounts[key] = count
}
mw.mu.RUnlock()
r := &Data{
Pid: mw.Pid,
UpTime: uptime.String(),
UpTimeSec: uptime.Seconds(),
Time: now.String(),
TimeUnix: now.Unix(),
StatusCodeCount: responseCounts,
TotalStatusCodeCount: totalResponseCounts,
Count: count,
TotalCount: totalCount,
TotalResponseTime: totalResponseTime.String(),
TotalResponseSize: totalResponseSize,
TotalResponseTimeSec: totalResponseTime.Seconds(),
TotalMetricsCounts: totalMetricsCounts,
AverageResponseSize: averageResponseSize,
AverageResponseTime: averageResponseTime.String(),
AverageResponseTimeSec: averageResponseTime.Seconds(),
AverageMetricsTimers: metricsCounts,
}
return r
}