2020-02-11 00:26:04 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
2020-08-17 16:04:03 +00:00
|
|
|
ptypes "github.com/traefik/paerser/types"
|
2020-09-16 13:46:04 +00:00
|
|
|
"github.com/traefik/traefik/v2/pkg/config/static"
|
|
|
|
"github.com/traefik/traefik/v2/pkg/udp"
|
2020-02-11 00:26:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestShutdownUDPConn(t *testing.T) {
|
2021-01-07 16:16:03 +00:00
|
|
|
ep := static.EntryPoint{
|
2020-02-11 00:26:04 +00:00
|
|
|
Address: ":0",
|
|
|
|
Transport: &static.EntryPointsTransport{
|
|
|
|
LifeCycle: &static.LifeCycle{
|
2020-08-17 16:04:03 +00:00
|
|
|
GraceTimeOut: ptypes.Duration(5 * time.Second),
|
2020-02-11 00:26:04 +00:00
|
|
|
},
|
|
|
|
},
|
2021-01-07 16:16:03 +00:00
|
|
|
}
|
|
|
|
ep.SetDefaults()
|
|
|
|
|
|
|
|
entryPoint, err := NewUDPEntryPoint(&ep)
|
2020-02-11 00:26:04 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
go entryPoint.Start(context.Background())
|
|
|
|
entryPoint.Switch(udp.HandlerFunc(func(conn *udp.Conn) {
|
|
|
|
for {
|
|
|
|
b := make([]byte, 1024*1024)
|
|
|
|
n, err := conn.Read(b)
|
2022-12-07 09:56:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// We control the termination, otherwise we would block on the Read above,
|
|
|
|
// until conn is closed by a timeout.
|
|
|
|
// Which means we would get an error,
|
|
|
|
// and even though we are in a goroutine and the current test might be over,
|
|
|
|
// go test would still yell at us if this happens while other tests are still running.
|
2020-02-11 00:26:04 +00:00
|
|
|
if string(b[:n]) == "CLOSE" {
|
|
|
|
return
|
|
|
|
}
|
2022-12-07 09:56:05 +00:00
|
|
|
_, _ = conn.Write(b[:n])
|
2020-02-11 00:26:04 +00:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
|
|
|
|
conn, err := net.Dial("udp", entryPoint.listener.Addr().String())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Start sending packets, to create a "session" with the server.
|
|
|
|
requireEcho(t, "TEST", conn, time.Second)
|
|
|
|
|
|
|
|
doneChan := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
entryPoint.Shutdown(context.Background())
|
|
|
|
close(doneChan)
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Make sure that our session is still live even after the shutdown.
|
|
|
|
requireEcho(t, "TEST2", conn, time.Second)
|
|
|
|
|
|
|
|
// And make sure that on the other hand, opening new sessions is not possible anymore.
|
|
|
|
conn2, err := net.Dial("udp", entryPoint.listener.Addr().String())
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = conn2.Write([]byte("TEST"))
|
|
|
|
// Packet is accepted, but dropped
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-12-07 09:56:05 +00:00
|
|
|
// Make sure that our session is yet again still live.
|
|
|
|
// This is specifically to make sure we don't create a regression in listener's readLoop,
|
|
|
|
// i.e. that we only terminate the listener's readLoop goroutine by closing its pConn.
|
2020-02-11 00:26:04 +00:00
|
|
|
requireEcho(t, "TEST3", conn, time.Second)
|
|
|
|
|
|
|
|
done := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
defer close(done)
|
|
|
|
b := make([]byte, 1024*1024)
|
|
|
|
n, err := conn2.Read(b)
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, 0, n)
|
|
|
|
}()
|
|
|
|
|
|
|
|
conn2.Close()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
case <-time.Tick(time.Second):
|
|
|
|
t.Fatal("Timeout")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = conn.Write([]byte("CLOSE"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-doneChan:
|
2020-03-18 13:50:06 +00:00
|
|
|
case <-time.Tick(10 * time.Second):
|
2020-02-11 00:26:04 +00:00
|
|
|
// In case we introduce a regression that would make the test wait forever.
|
|
|
|
t.Fatal("Timeout during shutdown")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-07 09:56:05 +00:00
|
|
|
// requireEcho tests that conn session is live and functional,
|
|
|
|
// by writing data through it,
|
|
|
|
// and expecting the same data as a response when reading on it.
|
|
|
|
// It fatals if the read blocks longer than timeout,
|
|
|
|
// which is useful to detect regressions that would make a test wait forever.
|
2020-02-11 00:26:04 +00:00
|
|
|
func requireEcho(t *testing.T, data string, conn io.ReadWriter, timeout time.Duration) {
|
2020-12-29 09:54:03 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2020-02-11 00:26:04 +00:00
|
|
|
_, err := conn.Write([]byte(data))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
doneChan := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
b := make([]byte, 1024*1024)
|
|
|
|
n, err := conn.Read(b)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, data, string(b[:n]))
|
|
|
|
close(doneChan)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-doneChan:
|
|
|
|
case <-time.Tick(timeout):
|
|
|
|
t.Fatalf("Timeout during echo for: %s", data)
|
|
|
|
}
|
|
|
|
}
|