mirror of https://github.com/knative/pkg.git
🐛 `TestDialWithBackoff` work without special environment (#2402)
* Making TestDialWithBackoff work without special environment * Use listen to force a connection timeout (#1) * Don't parallelize tests to avoid free port conflicts Co-authored-by: Evan Anderson <evan.k.anderson@gmail.com>
This commit is contained in:
parent
12be06090b
commit
45c37c266b
|
|
@ -55,6 +55,9 @@ var backOffTemplate = wait.Backoff{
|
|||
Steps: 15,
|
||||
}
|
||||
|
||||
// ErrTimeoutDialing when the timeout is reached after set amount of time.
|
||||
var ErrTimeoutDialing = errors.New("timed out dialing")
|
||||
|
||||
// DialWithBackOff executes `net.Dialer.DialContext()` with exponentially increasing
|
||||
// dial timeouts. In addition it sleeps with random jitter between tries.
|
||||
var DialWithBackOff = NewBackoffDialer(backOffTemplate)
|
||||
|
|
@ -110,7 +113,7 @@ func dialBackOffHelper(ctx context.Context, network, address string, bo wait.Bac
|
|||
return c, nil
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
return nil, fmt.Errorf("timed out dialing after %.2fs", elapsed.Seconds())
|
||||
return nil, fmt.Errorf("%w after %.2fs", ErrTimeoutDialing, elapsed.Seconds())
|
||||
}
|
||||
|
||||
func newHTTPTransport(disableKeepAlives, disableCompression bool, maxIdle, maxIdlePerHost int) http.RoundTripper {
|
||||
|
|
|
|||
|
|
@ -20,18 +20,19 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
timeoutErr = "timed out dialing"
|
||||
connectionRefusedErr = "connection refused"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
func TestHTTPRoundTripper(t *testing.T) {
|
||||
|
|
@ -76,67 +77,187 @@ func TestHTTPRoundTripper(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDialWithBackoff(t *testing.T) {
|
||||
// Make the test short.
|
||||
bo := backOffTemplate
|
||||
bo.Steps = 2
|
||||
func TestDialWithBackoffConnectionRefused(t *testing.T) {
|
||||
testDialWithBackoffConnectionRefused(nil, t)
|
||||
}
|
||||
|
||||
// Nobody's listening on a random port. Usually.
|
||||
c, err := dialBackOffHelper(context.Background(), "tcp4", "127.0.0.1:41482", bo, nil)
|
||||
verifyFailedConnection(t, c, err, connectionRefusedErr)
|
||||
func TestDialWithBackoffTimeout(t *testing.T) {
|
||||
testDialWithBackoffTimeout(nil, t)
|
||||
}
|
||||
|
||||
// Timeout. Use special testing IP address.
|
||||
c, err = dialBackOffHelper(context.Background(), "tcp4", "198.18.0.254:8888", bo, nil)
|
||||
verifyFailedConnection(t, c, err, timeoutErr)
|
||||
func TestDialWithBackoffSuccess(t *testing.T) {
|
||||
testDialWithBackoffSuccess(nil, t)
|
||||
}
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
func TestDialTLSWithBackoffConnectionRefused(t *testing.T) {
|
||||
testDialWithBackoffConnectionRefused(exampleTlsConf(), t)
|
||||
}
|
||||
|
||||
func TestDialTLSWithBackoffTimeout(t *testing.T) {
|
||||
testDialWithBackoffTimeout(exampleTlsConf(), t)
|
||||
}
|
||||
|
||||
func TestDialTLSWithBackoffSuccess(t *testing.T) {
|
||||
testDialWithBackoffSuccess(exampleTlsConf(), t)
|
||||
}
|
||||
|
||||
func testDialWithBackoffConnectionRefused(tlsConf *tls.Config, t testingT) {
|
||||
ctx := context.TODO()
|
||||
port := findUnusedPortOrFail(t)
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
dialer := newDialer(ctx, tlsConf)
|
||||
c, err := dialer(addr)
|
||||
closeOrFail(t, c)
|
||||
if !errors.Is(err, syscall.ECONNREFUSED) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDialWithBackoffTimeout(tlsConf *tls.Config, t testingT) {
|
||||
ctx := context.TODO()
|
||||
closer, addr, err := listenOne()
|
||||
if err != nil {
|
||||
t.Fatal("Unable to create listener:", err)
|
||||
}
|
||||
defer closer()
|
||||
c1, err := net.Dial("tcp4", addr.String())
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to connect to server on %s: %s", addr, err)
|
||||
}
|
||||
defer closeOrFail(t, c1)
|
||||
|
||||
// Since the backlog is full, the next request must time out.
|
||||
dialer := newDialer(ctx, tlsConf)
|
||||
c, err := dialer(addr.String())
|
||||
if err == nil {
|
||||
closeOrFail(t, c)
|
||||
t.Fatal("Unexpected success dialing")
|
||||
}
|
||||
if !errors.Is(err, ErrTimeoutDialing) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDialWithBackoffSuccess(tlsConf *tls.Config, t testingT) {
|
||||
//goland:noinspection HttpUrlsUsage
|
||||
const (
|
||||
prefixHTTP = "http://"
|
||||
prefixHTTPS = "https://"
|
||||
)
|
||||
ctx := context.TODO()
|
||||
var s *httptest.Server
|
||||
servFn := httptest.NewServer
|
||||
if tlsConf != nil {
|
||||
servFn = httptest.NewTLSServer
|
||||
}
|
||||
s = servFn(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
defer s.Close()
|
||||
prefix := prefixHTTP
|
||||
if tlsConf != nil {
|
||||
prefix = prefixHTTPS
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AddCert(s.Certificate())
|
||||
tlsConf.RootCAs = rootCAs
|
||||
}
|
||||
addr := strings.TrimPrefix(s.URL, prefix)
|
||||
|
||||
c, err = DialWithBackOff(context.Background(), "tcp4", strings.TrimPrefix(s.URL, "http://"))
|
||||
dialer := newDialer(ctx, tlsConf)
|
||||
c, err := dialer(addr)
|
||||
if err != nil {
|
||||
t.Fatal("Dial error =", err)
|
||||
}
|
||||
c.Close()
|
||||
closeOrFail(t, c)
|
||||
}
|
||||
|
||||
func TestDialTLSWithBackoff(t *testing.T) {
|
||||
// Make the test short.
|
||||
bo := backOffTemplate
|
||||
bo.Steps = 2
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
func exampleTlsConf() *tls.Config {
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
ServerName: "example.com",
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
// Nobody's listening on a random port. Usually.
|
||||
c, err := dialBackOffHelper(context.Background(), "tcp4", "127.0.0.1:41482", bo, tlsConf)
|
||||
verifyFailedConnection(t, c, err, connectionRefusedErr)
|
||||
func newDialer(ctx context.Context, tlsConf *tls.Config) func(addr string) (net.Conn, error) {
|
||||
// Make the test short.
|
||||
bo := wait.Backoff{
|
||||
Duration: time.Millisecond,
|
||||
Factor: 1.4,
|
||||
Jitter: 0.1, // At most 10% jitter.
|
||||
Steps: 1,
|
||||
}
|
||||
|
||||
// Timeout. Use special testing IP address.
|
||||
c, err = dialBackOffHelper(context.Background(), "tcp4", "198.18.0.254:8888", bo, tlsConf)
|
||||
verifyFailedConnection(t, c, err, timeoutErr)
|
||||
dialFn := func(addr string) (net.Conn, error) {
|
||||
return NewBackoffDialer(bo)(ctx, "tcp4", addr)
|
||||
}
|
||||
if tlsConf != nil {
|
||||
dialFn = func(addr string) (net.Conn, error) {
|
||||
bo.Duration = 10 * time.Millisecond
|
||||
bo.Steps = 3
|
||||
return NewTLSBackoffDialer(bo)(ctx, "tcp4", addr, tlsConf)
|
||||
}
|
||||
}
|
||||
return dialFn
|
||||
}
|
||||
|
||||
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
defer s.Close()
|
||||
func closeOrFail(t testingT, con io.Closer) {
|
||||
if con == nil {
|
||||
return
|
||||
}
|
||||
if err := con.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AddCert(s.Certificate())
|
||||
tlsConf.RootCAs = rootCAs
|
||||
|
||||
c, err = DialTLSWithBackOff(context.Background(), "tcp4", strings.TrimPrefix(s.URL, "https://"), tlsConf)
|
||||
func findUnusedPortOrFail(t testingT) int {
|
||||
l, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal("Dial error =", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer closeOrFail(t, l)
|
||||
return l.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func verifyFailedConnection(t *testing.T, c net.Conn, err error, prefix string) {
|
||||
if err == nil {
|
||||
c.Close()
|
||||
t.Error("Unexpected success dialing")
|
||||
} else if !strings.Contains(err.Error(), prefix) {
|
||||
t.Errorf("Error = %v, want: %s(...)", err, prefix)
|
||||
var errTest = errors.New("testing")
|
||||
|
||||
func newTestErr(msg string, err error) error {
|
||||
return fmt.Errorf("%w: %s: %v", errTest, msg, err)
|
||||
}
|
||||
|
||||
// listenOne creates a socket with backlog of one, and use that socket, so
|
||||
// any other connection will guarantee to timeout.
|
||||
//
|
||||
// Golang doesn't allow us to set the backlog argument on syscall.Listen from
|
||||
// net.ListenTCP, so we need to get directly into syscall land.
|
||||
func listenOne() (func(), *net.TCPAddr, error) {
|
||||
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, nil, newTestErr("Couldn't get socket", err)
|
||||
}
|
||||
sa := &syscall.SockaddrInet4{
|
||||
Port: 0,
|
||||
Addr: [4]byte{127, 0, 0, 1},
|
||||
}
|
||||
if err = syscall.Bind(fd, sa); err != nil {
|
||||
return nil, nil, newTestErr("Unable to bind", err)
|
||||
}
|
||||
if err = syscall.Listen(fd, 0); err != nil {
|
||||
return nil, nil, newTestErr("Unable to Listen", err)
|
||||
}
|
||||
closer := func() { _ = syscall.Close(fd) }
|
||||
listenaddr, err := syscall.Getsockname(fd)
|
||||
if err != nil {
|
||||
closer()
|
||||
return nil, nil, newTestErr("Could not get sockname", err)
|
||||
}
|
||||
sa = listenaddr.(*syscall.SockaddrInet4)
|
||||
addr := &net.TCPAddr{
|
||||
IP: sa.Addr[:],
|
||||
Port: sa.Port,
|
||||
}
|
||||
return closer, addr, nil
|
||||
}
|
||||
|
||||
type testingT interface {
|
||||
Fatal(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue