UDP support
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
parent
8988c8f9af
commit
115d42e0f0
72 changed files with 4730 additions and 321 deletions
|
@ -49,3 +49,30 @@
|
|||
|
||||
[tls.options.baz]
|
||||
minversion = "VersionTLS11"
|
||||
|
||||
[tcp.routers]
|
||||
[tcp.routers.router3]
|
||||
entrypoints=["unknown-entrypoint"]
|
||||
service = "service1"
|
||||
rule = "HostSNI(`mydomain.com`)"
|
||||
[tcp.routers.router4]
|
||||
entrypoints=["websecure"]
|
||||
service = "service1"
|
||||
rule = "Host(`mydomain.com`)"
|
||||
|
||||
[tcp.services]
|
||||
[tcp.services.service1]
|
||||
[tcp.services.service1.loadBalancer]
|
||||
[[tcp.services.service1.loadBalancer.servers]]
|
||||
address = "127.0.0.1:9010"
|
||||
|
||||
[udp.routers]
|
||||
[udp.routers.router3]
|
||||
entrypoints=["unknown-entrypoint"]
|
||||
service = "service1"
|
||||
|
||||
[udp.services]
|
||||
[udp.services.service1]
|
||||
[udp.services.service1.loadBalancer]
|
||||
[[udp.services.service1.loadBalancer.servers]]
|
||||
address = "127.0.0.1:9010"
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
[entryPoints]
|
||||
[entryPoints.websecure]
|
||||
address = ":4443"
|
||||
[entryPoints.udp]
|
||||
address = ":4443/udp"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
@ -33,3 +35,35 @@
|
|||
[http.services.service2.loadBalancer]
|
||||
[[http.services.service2.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9010"
|
||||
|
||||
[tcp.routers]
|
||||
[tcp.routers.router4]
|
||||
service = "service1"
|
||||
rule = "HostSNI(`snitest.net`)"
|
||||
|
||||
[tcp.routers.router5]
|
||||
service = "service2"
|
||||
rule = "HostSNI(`snitest.com`)"
|
||||
|
||||
[tcp.services]
|
||||
[tcp.services.service1]
|
||||
|
||||
[tcp.services.service2]
|
||||
[tcp.services.service2.loadBalancer]
|
||||
[[tcp.services.service2.loadBalancer.servers]]
|
||||
address = "127.0.0.1:9010"
|
||||
|
||||
[udp.routers]
|
||||
[udp.routers.router4]
|
||||
service = "service1"
|
||||
|
||||
[udp.routers.router5]
|
||||
service = "service2"
|
||||
|
||||
[udp.services]
|
||||
[udp.services.service1]
|
||||
|
||||
[udp.services.service2]
|
||||
[udp.services.service2.loadBalancer]
|
||||
[[udp.services.service2.loadBalancer.servers]]
|
||||
address = "127.0.0.1:9010"
|
||||
|
|
53
integration/fixtures/udp/wrr.toml
Normal file
53
integration/fixtures/udp/wrr.toml
Normal file
|
@ -0,0 +1,53 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.udp]
|
||||
address = ":8093/udp"
|
||||
[entryPoints.web]
|
||||
address = ":8093"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
[udp]
|
||||
[udp.routers]
|
||||
[udp.routers.to-whoami-a]
|
||||
service = "whoami"
|
||||
entryPoints = [ "udp" ]
|
||||
|
||||
[[udp.services.whoami.weighted.services]]
|
||||
name="whoami-a"
|
||||
weight=3
|
||||
[[udp.services.whoami.weighted.services]]
|
||||
name="whoami-b"
|
||||
weight=1
|
||||
|
||||
[udp.services.whoami-a.loadBalancer]
|
||||
[[udp.services.whoami-a.loadBalancer.servers]]
|
||||
address = "{{ .WhoamiAIP}}:8080"
|
||||
[[udp.services.whoami-a.loadBalancer.servers]]
|
||||
address = "{{ .WhoamiCIP}}:8080"
|
||||
|
||||
[udp.services.whoami-b.loadBalancer]
|
||||
[[udp.services.whoami-b.loadBalancer.servers]]
|
||||
address = "{{ .WhoamiBIP}}:8080"
|
||||
|
||||
[http]
|
||||
[http.routers]
|
||||
[http.routers.to-whoami-d]
|
||||
service = "whoami"
|
||||
entryPoints = [ "web" ]
|
||||
rule = "PathPrefix(`/who`)"
|
||||
|
||||
[http.services.whoami.loadBalancer]
|
||||
[[http.services.whoami.loadBalancer.servers]]
|
||||
url = "http://{{ .WhoamiDIP}}"
|
|
@ -60,6 +60,7 @@ func Test(t *testing.T) {
|
|||
check.Suite(&TimeoutSuite{})
|
||||
check.Suite(&TLSClientHeadersSuite{})
|
||||
check.Suite(&TracingSuite{})
|
||||
check.Suite(&UDPSuite{})
|
||||
check.Suite(&WebsocketSuite{})
|
||||
check.Suite(&ZookeeperSuite{})
|
||||
}
|
||||
|
|
14
integration/resources/compose/udp.yml
Normal file
14
integration/resources/compose/udp.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
whoami-a:
|
||||
image: containous/whoamiudp:dev
|
||||
command: -name whoami-a
|
||||
|
||||
whoami-b:
|
||||
image: containous/whoamiudp:dev
|
||||
command: -name whoami-b
|
||||
|
||||
whoami-c:
|
||||
image: containous/whoamiudp:dev
|
||||
command: -name whoami-c
|
||||
|
||||
whoami-d:
|
||||
image: containous/whoami
|
|
@ -549,6 +549,83 @@ func (s *SimpleSuite) TestServiceConfigErrors(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestTCPRouterConfigErrors(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// router3 has an error because it uses an unknown entrypoint
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// router4 has an unsupported Rule
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router4@file", 1000*time.Millisecond, try.BodyContains("unknown rule Host(`mydomain.com`)"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestTCPServiceConfigErrors(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/service_errors.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services", 1000*time.Millisecond, try.BodyContains(`["the service \"service1@file\" does not have any type defined"]`))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestUDPRouterConfigErrors(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/udp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestUDPServiceConfigErrors(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/service_errors.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.traefikCmd(withConfigFile(file))
|
||||
defer output(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/udp/services", 1000*time.Millisecond, try.BodyContains(`["the udp service \"service1@file\" does not have any type defined"]`))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/udp/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/udp/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWRR(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
s.composeProject.Start(c)
|
||||
|
|
107
integration/udp_test.go
Normal file
107
integration/udp_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/integration/try"
|
||||
"github.com/go-check/check"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
type UDPSuite struct{ BaseSuite }
|
||||
|
||||
func (s *UDPSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "udp")
|
||||
s.composeProject.Start(c)
|
||||
}
|
||||
|
||||
func guessWhoUDP(addr string) (string, error) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
udpAddr, err2 := net.ResolveUDPAddr("udp", addr)
|
||||
if err2 != nil {
|
||||
return "", err2
|
||||
}
|
||||
|
||||
conn, err = net.DialUDP("udp", nil, udpAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte("WHO"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
out := make([]byte, 2048)
|
||||
n, err := conn.Read(out)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out[:n]), nil
|
||||
}
|
||||
|
||||
func (s *UDPSuite) TestWRR(c *check.C) {
|
||||
whoamiAIP := s.composeProject.Container(c, "whoami-a").NetworkSettings.IPAddress
|
||||
whoamiBIP := s.composeProject.Container(c, "whoami-b").NetworkSettings.IPAddress
|
||||
whoamiCIP := s.composeProject.Container(c, "whoami-c").NetworkSettings.IPAddress
|
||||
whoamiDIP := s.composeProject.Container(c, "whoami-d").NetworkSettings.IPAddress
|
||||
|
||||
file := s.adaptFile(c, "fixtures/udp/wrr.toml", struct {
|
||||
WhoamiAIP string
|
||||
WhoamiBIP string
|
||||
WhoamiCIP string
|
||||
WhoamiDIP string
|
||||
}{
|
||||
WhoamiAIP: whoamiAIP,
|
||||
WhoamiBIP: whoamiBIP,
|
||||
WhoamiCIP: whoamiCIP,
|
||||
WhoamiDIP: whoamiDIP,
|
||||
})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("whoami-a"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8093/who", 5*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
stop := make(chan struct{})
|
||||
go func() {
|
||||
call := map[string]int{}
|
||||
for i := 0; i < 4; i++ {
|
||||
out, err := guessWhoUDP("127.0.0.1:8093")
|
||||
c.Assert(err, checker.IsNil)
|
||||
switch {
|
||||
case strings.Contains(out, "whoami-a"):
|
||||
call["whoami-a"]++
|
||||
case strings.Contains(out, "whoami-b"):
|
||||
call["whoami-b"]++
|
||||
case strings.Contains(out, "whoami-c"):
|
||||
call["whoami-c"]++
|
||||
default:
|
||||
call["unknown"]++
|
||||
}
|
||||
}
|
||||
c.Assert(call, checker.DeepEquals, map[string]int{"whoami-a": 2, "whoami-b": 1, "whoami-c": 1})
|
||||
close(stop)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
case <-time.Tick(time.Second * 5):
|
||||
c.Error("Timeout")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue