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

View file

@ -10,7 +10,7 @@ Let's see how.
### 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`

View file

@ -603,7 +603,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue)
func waitForTraefik(c *check.C, containerName string) {
// 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)
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()
// 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)
}

View file

@ -62,27 +62,23 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
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)
defer resp.Body.Close()
var services []api.ServiceRepresentation
err = json.NewDecoder(resp.Body).Decode(&services)
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)
var rtconf api.RunTimeRepresentation
err = json.NewDecoder(resp.Body).Decode(&rtconf)
c.Assert(err, checker.IsNil)
// 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(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)
s.stopAndRemoveContainerByName(c, "powpow")
@ -323,11 +323,11 @@ func (s *DockerSuite) TestRestartDockerContainers(c *check.C) {
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)
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)
}

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()
// 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)
var response string
@ -205,7 +205,7 @@ func (s *GRPCSuite) TestGRPCh2c(c *check.C) {
defer cmd.Process.Kill()
// 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)
var response string
@ -247,7 +247,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) {
defer cmd.Process.Kill()
// 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)
var response string
@ -289,7 +289,7 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
defer cmd.Process.Kill()
// 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)
var response string
@ -336,7 +336,7 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
defer cmd.Process.Kill()
// 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)
var client helloworld.Greeter_StreamExampleClient
client, closer, err := callStreamExampleClientGRPC()
@ -364,7 +364,6 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
stopStreamExample := make(chan bool)
lis, err := net.Listen("tcp", ":0")
c.Assert(err, check.IsNil)
_, port, err := net.SplitHostPort(lis.Addr().String())
@ -378,7 +377,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
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
KeyContent string
GRPCServerPort string
@ -396,7 +395,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
defer cmd.Process.Kill()
// 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)
var client helloworld.Greeter_StreamExampleClient
@ -454,7 +453,7 @@ func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) {
defer cmd.Process.Kill()
// 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)
var response string

View file

@ -41,7 +41,7 @@ func (s *HealthCheckSuite) TestSimpleConfiguration(c *check.C) {
defer cmd.Process.Kill()
// 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)
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()
// 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)
// Check entrypoint http1
@ -194,7 +194,7 @@ func (s *HealthCheckSuite) TestPortOverload(c *check.C) {
defer cmd.Process.Kill()
// 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)
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()
// 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)
tlsConfig := &tls.Config{
@ -66,7 +66,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
defer cmd.Process.Kill()
// 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)
backend1 := startTestServer("9010", http.StatusNoContent)
@ -122,7 +122,7 @@ func (s *HTTPSSuite) TestWithSNIStrictNotMatchedRequest(c *check.C) {
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -146,7 +146,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificate(c *check.C) {
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -180,7 +180,7 @@ func (s *HTTPSSuite) TestWithDefaultCertificateNoSNI(c *check.C) {
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -214,7 +214,7 @@ func (s *HTTPSSuite) TestWithOverlappingStaticCertificate(c *check.C) {
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -249,7 +249,7 @@ func (s *HTTPSSuite) TestWithOverlappingDynamicCertificate(c *check.C) {
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -282,7 +282,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -338,7 +338,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAs(c *check
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -399,7 +399,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipleCAsMultipleF
defer cmd.Process.Kill()
// 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)
tlsConfig := &tls.Config{
@ -464,7 +464,7 @@ func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend(c *check.C) {
defer cmd.Process.Kill()
// 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)
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()
// 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)
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
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)
backend1 := startTestServer("9010", http.StatusNoContent)
@ -613,7 +613,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
}
// 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)
backend1 := startTestServer("9010", http.StatusNoContent)
@ -676,7 +676,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c
}
// 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)
backend2 := startTestServer("9020", http.StatusResetContent)
@ -740,7 +740,7 @@ func (s *HTTPSSuite) TestEntrypointHttpsRedirectAndPathModification(c *check.C)
defer cmd.Process.Kill()
// 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)
client := &http.Client{
@ -841,7 +841,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
defer cmd.Process.Kill()
// 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)
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`)"))
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)
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)
}

View file

@ -11,6 +11,7 @@ server1:
- traefik.enable=true
- traefik.http.routers.rt-server1.entryPoints=web
- 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
server2:
image: containous/whoami

View file

@ -65,7 +65,7 @@ func (s *RestSuite) TestSimpleConfiguration(c *check.C) {
c.Assert(err, checker.IsNil)
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)
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)
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)
// 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)
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)
// 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)
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)
}
@ -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))
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)
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)
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))
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
testCases := []struct {
@ -415,7 +415,7 @@ func (s *SimpleSuite) TestXForwardedHeaders(c *check.C) {
c.Assert(err, checker.IsNil)
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"))
c.Assert(err, checker.IsNil)
@ -450,7 +450,7 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) {
c.Assert(err, checker.IsNil)
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)
config := config.HTTPConfiguration{
@ -474,7 +474,7 @@ func (s *SimpleSuite) TestMultiprovider(c *check.C) {
c.Assert(err, checker.IsNil)
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)
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)
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)
//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)
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)
// 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)
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)
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)
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))
c.Assert(err, checker.IsNil)
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)
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))
c.Assert(err, checker.IsNil)
@ -129,6 +137,10 @@ func (s *TracingSuite) TestZipkinAuth(c *check.C) {
c.Assert(err, checker.IsNil)
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))
c.Assert(err, checker.IsNil)

View file

@ -57,7 +57,7 @@ func (s *WebsocketSuite) TestBase(c *check.C) {
defer cmd.Process.Kill()
// 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)
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()
// 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)
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()
// 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)
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()
// 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)
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()
// 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)
// Add client self-signed cert
@ -339,7 +339,7 @@ func (s *WebsocketSuite) TestBasicAuth(c *check.C) {
defer cmd.Process.Kill()
// 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)
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()
// 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)
_, 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()
// 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)
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()
// 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)
// Add client self-signed cert
@ -543,7 +543,7 @@ func (s *WebsocketSuite) TestHeaderAreForwared(c *check.C) {
defer cmd.Process.Kill()
// 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)
headers := http.Header{}

View file

@ -6,79 +6,70 @@ import (
"github.com/containous/mux"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/types"
"github.com/containous/traefik/pkg/version"
assetfs "github.com/elazarl/go-bindata-assetfs"
thoasstats "github.com/thoas/stats"
"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"})
type jsonRenderer interface {
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
func (h Handler) Append(router *mux.Router) {
if h.Debug {
if h.debug {
DebugHandler{}.Append(router)
}
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRawData)
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)
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
// FIXME stats
// health route
@ -86,268 +77,29 @@ func (h Handler) Append(router *mux.Router) {
version.Handler{}.Append(router)
if h.Dashboard {
DashboardHandler{Assets: h.DashboardAssets}.Append(router)
if h.dashboard {
DashboardHandler{Assets: h.dashboardAssets}.Append(router)
}
}
func (h Handler) getRawData(rw http.ResponseWriter, request *http.Request) {
if h.CurrentConfigurations != nil {
currentConfigurations, ok := h.CurrentConfigurations.Get().(config.Configurations)
if !ok {
rw.WriteHeader(http.StatusOK)
return
}
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) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {
siRepr := make(map[string]*serviceInfoRepresentation, len(h.runtimeConfiguration.Services))
for k, v := range h.runtimeConfiguration.Services {
siRepr[k] = &serviceInfoRepresentation{
ServiceInfo: v,
ServerStatus: v.GetAllStatus(),
}
}
}
func (h Handler) getProvidersHandler(rw http.ResponseWriter, request *http.Request) {
// FIXME handle currentConfiguration
if h.CurrentConfigurations != nil {
currentConfigurations, ok := h.CurrentConfigurations.Get().(config.Configurations)
if !ok {
rw.WriteHeader(http.StatusOK)
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
rtRepr := RunTimeRepresentation{
Routers: h.runtimeConfiguration.Routers,
Middlewares: h.runtimeConfiguration.Middlewares,
Services: siRepr,
TCPRouters: h.runtimeConfiguration.TCPRouters,
TCPServices: h.runtimeConfiguration.TCPServices,
}
if provider.HTTP == nil {
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)
err := templateRenderer.JSON(rw, http.StatusOK, rtRepr)
if err != nil {
log.FromContext(request.Context()).Error(err)
http.Error(rw, err.Error(), http.StatusInternalServerError)

View file

@ -1,6 +1,8 @@
package api
import (
"encoding/json"
"flag"
"io/ioutil"
"net/http"
"net/http/httptest"
@ -8,188 +10,122 @@ import (
"github.com/containous/mux"
"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/require"
)
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
func TestHandler_Configuration(t *testing.T) {
type expected struct {
statusCode int
body string
json string
}
testCases := []struct {
desc string
path string
configuration config.Configurations
expected expected
desc string
path string
conf config.RuntimeConfiguration
expected expected
}{
{
desc: "Get all the providers",
path: "/api/providers",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"bar": {EntryPoints: []string{"foo", "bar"}},
},
},
},
},
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",
desc: "Get rawdata",
path: "/api/rawdata",
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",
},
},
},
},
},
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"}]}`},
},
{
desc: "Provider not found",
path: "/api/providers/foo",
configuration: config.Configurations{},
expected: expected{statusCode: http.StatusNotFound, body: "404 page not found\n"},
},
{
desc: "Get all routers",
path: "/api/providers/foo/routers",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"bar": {EntryPoints: []string{"foo", "bar"}},
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",
},
},
},
},
},
expected: expected{statusCode: http.StatusOK, body: `[{"entryPoints":["foo","bar"],"id":"bar"}]`},
},
{
desc: "Get a router",
path: "/api/providers/foo/routers/bar",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Routers: map[string]*config.Router{
"bar": {EntryPoints: []string{"foo", "bar"}},
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: expected{statusCode: http.StatusOK, body: `{"entryPoints":["foo","bar"]}`},
},
{
desc: "Router not found",
path: "/api/providers/foo/routers/bar",
configuration: config.Configurations{
"foo": {},
},
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",
TCPServices: map[string]*config.TCPServiceInfo{
"myprovider.tcpfoo-service": {
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{
Address: "127.0.0.1",
Weight: 1,
},
},
Method: "wrr",
},
},
},
},
},
expected: expected{statusCode: http.StatusOK, body: `[{"loadbalancer":{"method":"wrr","passHostHeader":false},"id":"foo"}]`},
},
{
desc: "Get a service",
path: "/api/providers/foo/services/foo",
configuration: config.Configurations{
"foo": {
HTTP: &config.HTTPConfiguration{
Services: map[string]*config.Service{
"foo": {
LoadBalancer: &config.LoadBalancerService{
Method: "wrr",
},
},
TCPRouters: map[string]*config.TCPRouterInfo{
"myprovider.tcpbar": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.tcpfoo-service",
Rule: "HostSNI(`foo.bar`)",
},
},
"myprovider.tcptest": {
TCPRouter: &config.TCPRouter{
EntryPoints: []string{"web"},
Service: "myprovider.tcpfoo-service",
Rule: "HostSNI(`foo.bar.other`)",
},
},
},
},
expected: expected{statusCode: http.StatusOK, body: `{"loadbalancer":{"method":"wrr","passHostHeader":false}}`},
},
{
desc: "Service not found",
path: "/api/providers/foo/services/bar",
configuration: config.Configurations{
"foo": {},
expected: expected{
statusCode: http.StatusOK,
json: "testdata/getrawdata.json",
},
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.Parallel()
currentConfiguration := &safe.Safe{}
currentConfiguration.Set(test.configuration)
handler := Handler{
CurrentConfigurations: currentConfiguration,
}
rtConf := &test.conf
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
router := mux.NewRouter()
handler.Append(router)
rtConf.PopulateUsedBy()
server := httptest.NewServer(router)
@ -215,12 +147,30 @@ func TestHandler_Configuration(t *testing.T) {
assert.Equal(t, test.expected.statusCode, resp.StatusCode)
content, err := ioutil.ReadAll(resp.Body)
contents, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
err = resp.Body.Close()
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"
"time"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/safe"
"github.com/go-kit/kit/metrics"
"github.com/vulcand/oxy/roundrobin"
)
const (
serverUp = "UP"
serverDown = "DOWN"
)
var singleton *HealthCheck
var once sync.Once
@ -221,3 +227,38 @@ func checkHealth(serverURL *url.URL, backend *BackendConfig) error {
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"
"time"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/testhelpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -367,6 +368,8 @@ type testLoadBalancer struct {
numRemovedServers int
numUpsertedServers int
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) {
@ -386,6 +389,7 @@ func (lb *testLoadBalancer) UpsertServer(u *url.URL, options ...roundrobin.Serve
defer lb.Unlock()
lb.numUpsertedServers++
lb.servers = append(lb.servers, u)
lb.options = append(lb.options, options...)
return nil
}
@ -393,14 +397,23 @@ func (lb *testLoadBalancer) Servers() []*url.URL {
return lb.servers
}
func (lb *testLoadBalancer) Options() []roundrobin.ServerOption {
return lb.options
}
func (lb *testLoadBalancer) removeServer(u *url.URL) {
var i int
var serverURL *url.URL
found := false
for i, serverURL = range lb.servers {
if *serverURL == *u {
found = true
break
}
}
if !found {
return
}
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()
}
}
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.
func NewBuilder(configs map[string]*config.Middleware) *Builder {
func NewBuilder(configs map[string]*config.MiddlewareInfo) *Builder {
return &Builder{configs: configs}
}
// Builder holds builder configuration.
type Builder struct {
configs map[string]*config.Middleware
configs map[string]*config.MiddlewareInfo
}
// 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.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)

View file

@ -39,7 +39,7 @@ const (
// Builder the middleware builder
type Builder struct {
configs map[string]*config.Middleware
configs map[string]*config.MiddlewareInfo
serviceBuilder serviceBuilder
}
@ -48,7 +48,7 @@ type serviceBuilder interface {
}
// 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}
}
@ -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) {
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)
}
var err error
if constructorContext, err = checkRecursion(constructorContext, middlewareName); err != nil {
b.configs[middlewareName].Err = err
return nil, err
}
constructor, err := b.buildConstructor(constructorContext, middlewareName, *b.configs[middlewareName])
constructor, err := b.buildConstructor(constructorContext, middlewareName)
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
@ -90,7 +99,9 @@ func checkRecursion(ctx context.Context, middlewareName string) (context.Context
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
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) {
testConfig := map[string]*config.Middleware{
testConfig := map[string]*config.MiddlewareInfo{
"empty": {},
}
middlewaresBuilder := NewBuilder(testConfig, nil)
@ -25,7 +25,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) {
}
func TestBuilder_BuildChainNonExistentChain(t *testing.T) {
testConfig := map[string]*config.Middleware{
testConfig := map[string]*config.MiddlewareInfo{
"foobar": {},
}
middlewaresBuilder := NewBuilder(testConfig, nil)
@ -264,7 +264,12 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
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)
@ -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 {
desc string
@ -344,7 +354,8 @@ func TestBuilder_buildConstructor(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
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)
middleware, err2 := constructor(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))

View file

@ -6,10 +6,10 @@ import (
"github.com/containous/alice"
"github.com/containous/mux"
"github.com/containous/traefik/pkg/api"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/metrics"
"github.com/containous/traefik/pkg/safe"
"github.com/containous/traefik/pkg/types"
)
@ -19,7 +19,8 @@ type chainBuilder interface {
}
// 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{}
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 {
chain := chainBuilder.BuildChain(ctx, conf.API.Middlewares)
aggregator.AddAppender(&WithMiddleware{
appender: api.Handler{
EntryPoint: conf.API.EntryPoint,
Dashboard: conf.API.Dashboard,
Statistics: conf.API.Statistics,
DashboardAssets: conf.API.DashboardAssets,
CurrentConfigurations: currentConfiguration,
Debug: conf.Global.Debug,
},
appender: api.New(conf, runtimeConfiguration),
routerMiddlewares: chain,
})
}
if conf.Ping != nil && conf.Ping.EntryPoint == entryPointName {

View file

@ -62,7 +62,7 @@ func TestNewRouteAppenderAggregator(t *testing.T) {
"/wrong": http.StatusBadGateway,
"/ping": http.StatusOK,
// "/.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 (
"context"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/config/static"
"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/types"
)
@ -27,8 +27,8 @@ type RouteAppenderFactory struct {
}
// NewAppender Creates a new RouteAppender
func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, currentConfiguration *safe.Safe) types.RouteAppender {
aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, currentConfiguration)
func (r *RouteAppenderFactory) NewAppender(ctx context.Context, middlewaresBuilder *middleware.Builder, runtimeConfiguration *config.RuntimeConfiguration) types.RouteAppender {
aggregator := NewRouteAppenderAggregator(ctx, middlewaresBuilder, r.staticConfiguration, r.entryPointName, runtimeConfiguration)
if r.acmeProvider != nil && r.acmeProvider.HTTPChallenge != nil && r.acmeProvider.HTTPChallenge.EntryPoint == r.entryPointName {
aggregator.AddAppender(r.acmeProvider)

View file

@ -23,7 +23,7 @@ const (
)
// 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,
) *Manager {
return &Manager{
@ -38,7 +38,7 @@ func NewManager(routers map[string]*config.Router,
// Manager A route/router manager
type Manager struct {
routerHandlers map[string]http.Handler
configs map[string]*config.Router
configs map[string]*config.RouterInfo
serviceManager *service.Manager
middlewaresBuilder *middleware.Builder
modifierBuilder *responsemodifiers.Builder
@ -75,8 +75,17 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
return entryPointHandlers
}
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*config.Router {
entryPointsRouters := make(map[string]map[string]*config.Router)
func contains(entryPoints []string, entryPointName string) bool {
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 {
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 {
entryPointsRouters[entryPointName] = make(map[string]*config.Router)
entryPointsRouters[entryPointName] = make(map[string]*config.RouterInfo)
}
entryPointsRouters[entryPointName][rtName] = rt
@ -105,7 +114,7 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string, tls
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()
if err != nil {
return nil, err
@ -117,12 +126,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
handler, err := m.buildRouterHandler(ctxRouter, routerName)
if err != nil {
routerConfig.Err = err.Error()
logger.Error(err)
continue
}
err = router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
if err != nil {
routerConfig.Err = err.Error()
logger.Error(err)
continue
}
@ -166,7 +177,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string) (ht
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))
for i, name := range router.Middlewares {
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)
}
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.Parallel()
serviceManager := service.NewManager(test.serviceConfig, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(test.middlewaresConfig, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(test.middlewaresConfig)
routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory)
rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Services: test.serviceConfig,
Routers: test.routersConfig,
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)
@ -413,11 +419,17 @@ func TestAccessLog(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
serviceManager := service.NewManager(test.serviceConfig, http.DefaultTransport)
middlewaresBuilder := middleware.NewBuilder(test.middlewaresConfig, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(test.middlewaresConfig)
routerManager := NewManager(test.routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory)
rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Services: test.serviceConfig,
Routers: test.routersConfig,
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)
@ -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 {
res *http.Response
}
@ -480,11 +796,17 @@ func BenchmarkRouterServe(b *testing.B) {
}
entryPoints := []string{"web"}
serviceManager := service.NewManager(serviceConfig, &staticTransport{res})
middlewaresBuilder := middleware.NewBuilder(map[string]*config.Middleware{}, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*config.Middleware{})
routerManager := NewManager(routersConfig, serviceManager, middlewaresBuilder, responseModifierFactory)
rtConf := config.NewRuntimeConfig(config.Configuration{
HTTP: &config.HTTPConfiguration{
Services: serviceConfig,
Routers: routersConfig,
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)
@ -498,6 +820,7 @@ func BenchmarkRouterServe(b *testing.B) {
}
}
func BenchmarkService(b *testing.B) {
res := &http.Response{
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()
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)

View file

@ -3,6 +3,7 @@ package tcp
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/containous/traefik/pkg/config"
@ -14,14 +15,14 @@ import (
)
// NewManager Creates a new Manager
func NewManager(routers map[string]*config.TCPRouter,
func NewManager(conf *config.RuntimeConfiguration,
serviceManager *tcpservice.Manager,
httpHandlers map[string]http.Handler,
httpsHandlers map[string]http.Handler,
tlsConfig *tls.Config,
) *Manager {
return &Manager{
configs: routers,
configs: conf.TCPRouters,
serviceManager: serviceManager,
httpHandlers: httpHandlers,
httpsHandlers: httpsHandlers,
@ -31,7 +32,7 @@ func NewManager(routers map[string]*config.TCPRouter,
// Manager is a route/router manager
type Manager struct {
configs map[string]*config.TCPRouter
configs map[string]*config.TCPRouterInfo
serviceManager *tcpservice.Manager
httpHandlers 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
}
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.HTTPHandler(handlerHTTP)
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)
if err != nil {
routerConfig.Err = err.Error()
logger.Error(err)
continue
}
domains, err := rules.ParseHostSNI(routerConfig.Rule)
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
}
for _, domain := range domains {
logger.Debugf("Add route %s on TCP", domain)
logger.Debugf("Adding route %s on TCP", domain)
switch {
case routerConfig.TLS != nil:
if routerConfig.TLS.Passthrough {
@ -101,8 +105,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return router, nil
}
func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map[string]map[string]*config.TCPRouter {
entryPointsRouters := make(map[string]map[string]*config.TCPRouter)
func contains(entryPoints []string, entryPointName string) bool {
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 {
eps := rt.EntryPoints
@ -118,7 +131,7 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map
}
if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*config.TCPRouter)
entryPointsRouters[entryPointName] = make(map[string]*config.TCPRouterInfo)
}
entryPointsRouters[entryPointName][rtName] = rt
@ -127,12 +140,3 @@ func (m *Manager) filteredRouters(ctx context.Context, entryPoints []string) map
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
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 {

View file

@ -69,47 +69,46 @@ func (s *Server) loadConfigurationTCP(configurations config.Configurations) map[
s.tlsManager.UpdateConfigs(conf.TLSStores, conf.TLSOptions, conf.TLS)
handlersNonTLS, handlersTLS := s.createHTTPHandlers(ctx, *conf.HTTP, entryPoints)
routersTCP := s.createTCPRouters(ctx, conf.TCP, entryPoints, handlersNonTLS, handlersTLS, s.tlsManager.Get("default", "default"))
rtConf := config.NewRuntimeConfig(conf)
handlersNonTLS, handlersTLS := s.createHTTPHandlers(ctx, rtConf, entryPoints)
routersTCP := s.createTCPRouters(ctx, rtConf, entryPoints, handlersNonTLS, handlersTLS, s.tlsManager.Get("default", "default"))
rtConf.PopulateUsedBy()
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 {
return make(map[string]*tcpCore.Router)
}
serviceManager := tcp.NewManager(configuration.Services)
routerManager := routertcp.NewManager(configuration.Routers, serviceManager, handlers, handlersTLS, tlsConfig)
serviceManager := tcp.NewManager(configuration)
routerManager := routertcp.NewManager(configuration, serviceManager, handlers, handlersTLS, tlsConfig)
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)
middlewaresBuilder := middleware.NewBuilder(configuration.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(configuration.Middlewares)
routerManager := router.NewManager(configuration.Routers, serviceManager, middlewaresBuilder, responseModifierFactory)
handlersNonTLS := routerManager.BuildHandlers(ctx, entryPoints, false)
handlersTLS := routerManager.BuildHandlers(ctx, entryPoints, true)
routerHandlers := make(map[string]http.Handler)
for _, entryPointName := range entryPoints {
internalMuxRouter := mux.NewRouter().
SkipClean(true)
internalMuxRouter := mux.NewRouter().SkipClean(true)
ctx = log.With(ctx, log.Str(log.EntryPointName, entryPointName))
factory := s.entryPointsTCP[entryPointName].RouteAppenderFactory
if factory != nil {
// FIXME remove currentConfigurations
appender := factory.NewAppender(ctx, middlewaresBuilder, &s.currentConfigurations)
appender := factory.NewAppender(ctx, middlewaresBuilder, configuration)
appender.Append(internalMuxRouter)
}

View file

@ -47,7 +47,8 @@ func TestReuseService(t *testing.T) {
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.
responseRecorderOk := &httptest.ResponseRecorder{}

View file

@ -258,7 +258,8 @@ func TestServerResponseEmptyBackend(t *testing.T) {
}
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{}
request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil)

View file

@ -26,7 +26,7 @@ const (
)
// 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{
bufferPool: newBufferPool(),
defaultRoundTripper: defaultRoundTripper,
@ -40,7 +40,7 @@ type Manager struct {
bufferPool httputil.BufferPool
defaultRoundTripper http.RoundTripper
balancers map[string][]healthcheck.BalancerHandler
configs map[string]*config.Service
configs map[string]*config.ServiceInfo
}
// 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)
ctx = internal.AddProviderInContext(ctx, serviceName)
if conf, ok := m.configs[serviceName]; ok {
// TODO Should handle multiple service types
// FIXME Check if the service is declared multiple times with different types
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)
conf, ok := m.configs[serviceName]
if !ok {
return nil, fmt.Errorf("the service %q does not exist", 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(
@ -158,7 +168,7 @@ func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler
}
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{
@ -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)
}

View file

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

View file

@ -13,13 +13,13 @@ import (
// Manager is the TCPHandlers factory
type Manager struct {
configs map[string]*config.TCPService
configs map[string]*config.TCPServiceInfo
}
// NewManager creates a new manager
func NewManager(configs map[string]*config.TCPService) *Manager {
func NewManager(conf *config.RuntimeConfiguration) *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 = 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]
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 {
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)
// FIXME Check if the service is declared multiple times with different types
loadBalancer := tcp.NewRRLoadBalancer()
for _, server := range conf.LoadBalancer.Servers {
if _, err := parseIP(server.Address); err != nil {
logger.Errorf("Invalid IP address for a %q server %q: %v", serviceQualifiedName, server.Address, err)
if _, _, err := net.SplitHostPort(server.Address); err != nil {
logger.Errorf("In service %q: %v", serviceQualifiedName, err)
continue
}
@ -57,20 +57,5 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
loadBalancer.AddServer(handler)
}
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"
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/server/internal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -12,49 +14,166 @@ func TestManager_BuildTCP(t *testing.T) {
testCases := []struct {
desc string
serviceName string
configs map[string]*config.TCPService
configs map[string]*config.TCPServiceInfo
providerName string
expectedError string
}{
{
desc: "without configuration",
serviceName: "test",
configs: nil,
expectedError: `the service "test" does not exits`,
expectedError: `the service "test" does not exist`,
},
{
desc: "missing lb configuration",
serviceName: "test",
configs: map[string]*config.TCPService{
"test": {},
configs: map[string]*config.TCPServiceInfo{
"test": {
TCPService: &config.TCPService{},
},
},
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",
configs: map[string]*config.TCPService{
configs: map[string]*config.TCPServiceInfo{
"test": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{Address: "test:31"},
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{Address: "test:31"},
},
},
},
},
},
},
{
desc: "invalid IP address",
desc: "invalid IP address, server is skipped, error is logged",
serviceName: "test",
configs: map[string]*config.TCPService{
configs: map[string]*config.TCPServiceInfo{
"test": {
LoadBalancer: &config.TCPLoadBalancerService{
Servers: []config.TCPServer{
{Address: "foobar"},
TCPService: &config.TCPService{
LoadBalancer: &config.TCPLoadBalancerService{
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 {
@ -62,19 +181,22 @@ func TestManager_BuildTCP(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
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 err == nil {
require.Error(t, err)
} else {
require.EqualError(t, err, test.expectedError)
require.Nil(t, handler)
}
assert.EqualError(t, err, test.expectedError)
require.Nil(t, handler)
} else {
require.NoError(t, err)
assert.Nil(t, err)
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
}