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,
|
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
|
// DialWithBackOff executes `net.Dialer.DialContext()` with exponentially increasing
|
||||||
// dial timeouts. In addition it sleeps with random jitter between tries.
|
// dial timeouts. In addition it sleeps with random jitter between tries.
|
||||||
var DialWithBackOff = NewBackoffDialer(backOffTemplate)
|
var DialWithBackOff = NewBackoffDialer(backOffTemplate)
|
||||||
|
|
@ -110,7 +113,7 @@ func dialBackOffHelper(ctx context.Context, network, address string, bo wait.Bac
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
elapsed := time.Since(start)
|
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 {
|
func newHTTPTransport(disableKeepAlives, disableCompression bool, maxIdle, maxIdlePerHost int) http.RoundTripper {
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,19 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
)
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
const (
|
|
||||||
timeoutErr = "timed out dialing"
|
|
||||||
connectionRefusedErr = "connection refused"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTTPRoundTripper(t *testing.T) {
|
func TestHTTPRoundTripper(t *testing.T) {
|
||||||
|
|
@ -76,67 +77,187 @@ func TestHTTPRoundTripper(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDialWithBackoff(t *testing.T) {
|
func TestDialWithBackoffConnectionRefused(t *testing.T) {
|
||||||
// Make the test short.
|
testDialWithBackoffConnectionRefused(nil, t)
|
||||||
bo := backOffTemplate
|
}
|
||||||
bo.Steps = 2
|
|
||||||
|
|
||||||
// Nobody's listening on a random port. Usually.
|
func TestDialWithBackoffTimeout(t *testing.T) {
|
||||||
c, err := dialBackOffHelper(context.Background(), "tcp4", "127.0.0.1:41482", bo, nil)
|
testDialWithBackoffTimeout(nil, t)
|
||||||
verifyFailedConnection(t, c, err, connectionRefusedErr)
|
}
|
||||||
|
|
||||||
// Timeout. Use special testing IP address.
|
func TestDialWithBackoffSuccess(t *testing.T) {
|
||||||
c, err = dialBackOffHelper(context.Background(), "tcp4", "198.18.0.254:8888", bo, nil)
|
testDialWithBackoffSuccess(nil, t)
|
||||||
verifyFailedConnection(t, c, err, timeoutErr)
|
}
|
||||||
|
|
||||||
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()
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Dial error =", err)
|
t.Fatal("Dial error =", err)
|
||||||
}
|
}
|
||||||
c.Close()
|
closeOrFail(t, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDialTLSWithBackoff(t *testing.T) {
|
func exampleTlsConf() *tls.Config {
|
||||||
// Make the test short.
|
return &tls.Config{
|
||||||
bo := backOffTemplate
|
|
||||||
bo.Steps = 2
|
|
||||||
|
|
||||||
tlsConf := &tls.Config{
|
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: false,
|
||||||
ServerName: "example.com",
|
ServerName: "example.com",
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Nobody's listening on a random port. Usually.
|
func newDialer(ctx context.Context, tlsConf *tls.Config) func(addr string) (net.Conn, error) {
|
||||||
c, err := dialBackOffHelper(context.Background(), "tcp4", "127.0.0.1:41482", bo, tlsConf)
|
// Make the test short.
|
||||||
verifyFailedConnection(t, c, err, connectionRefusedErr)
|
bo := wait.Backoff{
|
||||||
|
Duration: time.Millisecond,
|
||||||
|
Factor: 1.4,
|
||||||
|
Jitter: 0.1, // At most 10% jitter.
|
||||||
|
Steps: 1,
|
||||||
|
}
|
||||||
|
|
||||||
// Timeout. Use special testing IP address.
|
dialFn := func(addr string) (net.Conn, error) {
|
||||||
c, err = dialBackOffHelper(context.Background(), "tcp4", "198.18.0.254:8888", bo, tlsConf)
|
return NewBackoffDialer(bo)(ctx, "tcp4", addr)
|
||||||
verifyFailedConnection(t, c, err, timeoutErr)
|
}
|
||||||
|
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) {}))
|
func closeOrFail(t testingT, con io.Closer) {
|
||||||
defer s.Close()
|
if con == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := con.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rootCAs := x509.NewCertPool()
|
func findUnusedPortOrFail(t testingT) int {
|
||||||
rootCAs.AddCert(s.Certificate())
|
l, err := net.Listen("tcp", "localhost:0")
|
||||||
tlsConf.RootCAs = rootCAs
|
|
||||||
|
|
||||||
c, err = DialTLSWithBackOff(context.Background(), "tcp4", strings.TrimPrefix(s.URL, "https://"), tlsConf)
|
|
||||||
if err != nil {
|
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) {
|
var errTest = errors.New("testing")
|
||||||
if err == nil {
|
|
||||||
c.Close()
|
func newTestErr(msg string, err error) error {
|
||||||
t.Error("Unexpected success dialing")
|
return fmt.Errorf("%w: %s: %v", errTest, msg, err)
|
||||||
} else if !strings.Contains(err.Error(), prefix) {
|
|
||||||
t.Errorf("Error = %v, want: %s(...)", err, prefix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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