Fix tcp health check tests

This commit is contained in:
Douglas De Toni Machado 2025-03-29 15:20:28 -03:00
parent 7f33218331
commit 6a53b26517
2 changed files with 92 additions and 52 deletions

View File

@ -101,7 +101,7 @@ func (thc *ServiceTCPHealthChecker) Check(ctx context.Context) {
} }
} }
func (thc *ServiceTCPHealthChecker) executeHealthCheck(ctx context.Context, config *dynamic.TCPServerHealthCheck, target *net.TCPAddr) error { func (thc *ServiceTCPHealthChecker) executeHealthCheck(_ context.Context, config *dynamic.TCPServerHealthCheck, target *net.TCPAddr) error {
dialer, err := thc.dialerManager.Get(config.ServersTransport, config.TLS) dialer, err := thc.dialerManager.Get(config.ServersTransport, config.TLS)
if err != nil { if err != nil {
return err return err
@ -122,6 +122,7 @@ func (thc *ServiceTCPHealthChecker) executeHealthCheck(ctx context.Context, conf
if config.Expected != "" { if config.Expected != "" {
buf := make([]byte, len(config.Expected)) buf := make([]byte, len(config.Expected))
conn.SetReadDeadline(time.Now().Add(thc.timeout))
_, err = conn.Read(buf) _, err = conn.Read(buf)
if err != nil { if err != nil {
return err return err

View File

@ -3,14 +3,15 @@ package healthcheck
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"net" "net"
"net/netip"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types" ptypes "github.com/traefik/paerser/types"
@ -21,35 +22,52 @@ import (
) )
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE----- var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIICDDCCAXWgAwIBAgIQH20JmcOlcRWHNuf62SYwszANBgkqhkiG9w0BAQsFADAS MIIDJzCCAg+gAwIBAgIUe3vnWg3cTbflL6kz2TyPUxmV8Y4wDQYJKoZIhvcNAQEL
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wIBcNMjUwMzA1MjAwOTM4WhgPMjA1
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB NTAyMjYyMDA5MzhaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG
iQKBgQC0qINy3F4oq6viDnlpDDE5J08iSRGggg6EylJKBKZfphEG2ufgK78Dufl3 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Mm4Sp6xzJvFZJWAv/KVmI1krywiuef8Fhlf
+7b0LlEY2AeZHwviHODqC9a6ihj1ZYQk0/djAh+OeOhFEWu+9T/VP8gVFarFqT8D JR2M0caKixjBcNt4U8KwrzIrqL+8nilbps1QuwpQ09+6ztlbUXUL6DqR8ZC+4oCp
Opy+hrG7YJivUIzwb4fmJQRI7FajzsnGyM6LiXLU+0qzb7ZO/QIDAQABo2EwXzAO gOZ3yyVX2vhMigkATbQyJrX/WVjWSHD5rIUBP2BrsaYLt1qETnFP9wwQ3YEi7V4l
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw c4+jDrZOtJvrv+tRClt9gQJVgkr7Y30X+dx+rsh+ROaA2+/VTDX0qtoqd/4fjhcJ
AwEB/zAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0G OY9VLm0eU66VUMyOTNeUm6ZAXRBp/EonIM1FXOlj82S0pZQbPrvyWWqWoAjtPvLU
CSqGSIb3DQEBCwUAA4GBAB+eluoQYzyyMfeEEAOtlldevx5MtDENT05NB0WI+91R qRzqp/BQJqx3EHz1dP6s+xUjP999B+7jhiHoFhZ/bfVVlx8XkwIDAQABo2swaTAd
we7mX8lv763u0XuCWPxbHszhclI6FFjoQef0Z1NYLRm8ZRq58QqWDFZ3E6wdDK+B BgNVHQ4EFgQUhJiJ37LW6RODCpBPAApG1zQxFtAwHwYDVR0jBBgwFoAUhJiJ37LW
+OWvkW+hRavo6R9LzIZPfbv8yBo4M9PK/DXw8hLqH7VkkI+Gh793iH7Ugd4A7wvT 6RODCpBPAApG1zQxFtAwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREEDzANggtleGFt
cGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAfnDPHllA1TFlQ6zY46tqM20d68bR
kXeGMKLoaATFPbDea5H8/GM5CU6CPD7RUuEB9CvxvaM0aOInxkgstozG7BOr8hcs
WS9fMgM0oO5yGiSOv+Qa0Rc0BFb6A1fUJRta5MI5DTdTJLoyoRX/5aocSI34T67x
ULbkJvVXw6hnx/KZ65apNobfmVQSy7DR8Fo82eB4hSoaLpXyUUTLmctGgrRCoKof
GVUJfKsDJ4Ts8WIR1np74flSoxksWSHEOYk79AZOPANYgJwPMMiiZKsKm17GBoGu
DxI0om4eX8GaSSZAtG6TOt3O3v1oCjKNsAC+u585HN0x0MFA33TUzC15NA==
-----END CERTIFICATE-----`) -----END CERTIFICATE-----`)
// LocalhostKey is the private key for localhostCert.
var LocalhostKey = []byte(`-----BEGIN PRIVATE KEY----- var LocalhostKey = []byte(`-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALSog3LcXiirq+IO MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgybhKnrHMm8Vk
eWkMMTknTyJJEaCCDoTKUkoEpl+mEQba5+ArvwO5+Xf7tvQuURjYB5kfC+Ic4OoL lYC/8pWYjWSvLCK55/wWGV8lHYzRxoqLGMFw23hTwrCvMiuov7yeKVumzVC7ClDT
1rqKGPVlhCTT92MCH4546EURa771P9U/yBUVqsWpPwM6nL6GsbtgmK9QjPBvh+Yl 37rO2VtRdQvoOpHxkL7igKmA5nfLJVfa+EyKCQBNtDImtf9ZWNZIcPmshQE/YGux
BEjsVqPOycbIzouJctT7SrNvtk79AgMBAAECgYB1wMT1MBgbkFIXpXGTfAP1id61 pgu3WoROcU/3DBDdgSLtXiVzj6MOtk60m+u/61EKW32BAlWCSvtjfRf53H6uyH5E
rUTVBxCpkypx3ngHLjo46qRq5Hi72BN4FlTY8fugIudI8giP2FztkMvkiLDc4m0p 5oDb79VMNfSq2ip3/h+OFwk5j1UubR5TrpVQzI5M15SbpkBdEGn8SicgzUVc6WPz
Gn+QMJzjlBjjTuNLvLy4aSmNRLIC3mtbx9PdU71DQswEpJHFj/vmsxbuSrG1I1YE ZLSllBs+u/JZapagCO0+8tSpHOqn8FAmrHcQfPV0/qz7FSM/330H7uOGIegWFn9t
r1reuSo2ow6fOAjXLQJBANpz+RkOiPSPuvl+gi1sp2pLuynUJVDVqWZi386YRpfg 9VWXHxeTAgMBAAECggEALinfGhv7Iaz/3cdCOKlGBZ1MBxmGTC2TPKqbOpEWAWLH
DiKCLpqwqYDkOozm/fwFALvwXKGmsyyL43HO8eI+2NsCQQDTtY32V+02GPecdsyq wwcjetznmjQKewBPrQkrYEPYGapioPbeYJS61Y4XzeO+vUOCA10ZhoSrytgJ1ANo
msK06EPVTSaYwj9Mm+q709KsmYFHLXDqXjcKV4UgKYKRPz7my1fXodMmGmfuh1a3 RoTlmxd8I3kVL5QCy8ONxjTFYaOy/OP9We9iypXhRAbLSE4HDKZfmOXTxSbDctql
/HMHAkEAmOQKN0tA90mRJwUvvvMIyRBv0fq0kzq28P3KfiF9ZtZdjjFmxMVYHOmf Kq7uV3LX1KCfr9C6M8d79a0Rdr4p8IXp8MOg3tXq6n75vZbepRFyAujhg7o/kkTp
QPZ6VGR7+w1jB5BQXqEZcpHQIPSzeQJBAIy9tZJ/AYNlNbcegxEnsSjy/6VdlLsY lgv87h89lrK97K+AjqtvCIT3X3VXfA+LYp3AoQFdOluKgyJT221MyHkTeI/7gggt
51vWi0Yym2uC4R6gZuBnoc+OP0ISVmqY0Qg9RjhjrCs4gr9f2ZaWjSECQCxqZMq1 Z57lVGD71UJH/LGUJWrraJqXd9uDxZWprD/s66BIAQKBgQD8CtHUJ/VuS7gP0ebN
3viJ8BGCC0m/5jv1EHur3YgwphYCkf4Li6DKwIdMLk1WXkTcPIY3V2Jqj8rPEB5V 688zrmRtENj6Gqi+URm/Pwgr9b7wKKlf9jjhg5F/ue+BgB7/nK6N7yJ4Xx3JJ5ox
rqPRSAtd/h6oZbs= LqsRGLFa4fDBxogF/FN27obD8naOxe2wS1uTjM6LSrvdJ+HjeNEwHYhjuDjTAHj5
VVEMagZWgkE4jBiFUYefiYLsAQKBgQDkUVdW8cXaYri5xxDW86JNUzI1tUPyd6I+
AkOHV/V0y2zpwTHVLcETRpdVGpc5TH3J5vWf+5OvSz6RDTGjv7blDb8vB/kVkFmn
uXTi0dB9P+SYTsm+X3V7hOAFsyVYZ1D9IFsKUyMgxMdF+qgERjdPKx5IdLV/Jf3q
P9pQ922TkwKBgCKllhyU9Z8Y14+NKi4qeUxAb9uyUjFnUsT+vwxULNpmKL44yLfB
UCZoAKtPMwZZR2mZ70Dhm5pycNTDFeYm5Ssvesnkf0UT9oTkH9EcjvgGr5eGy9rN
MSSCWa46MsL/BYVQiWkU1jfnDiCrUvXrbX3IYWCo/TA5yfEhuQQMUiwBAoGADyzo
5TqEsBNHu/FjSSZAb2tMNw2pSoBxJDX6TxClm/G5d4AD0+uKncFfZaSy0HgpFDZp
tQx/sHML4ZBC8GNZwLe9MV8SS0Cg9Oj6v+i6Ntj8VLNH7YNix6b5TOevX8TeOTTh
WDpWZ2Ms65XRfRc9reFrzd0UAzN/QQaleCQ6AEkCgYBe4Ucows7JGbv7fNkz3nb1
kyH+hk9ecnq/evDKX7UUxKO1wwTi74IYKgcRB2uPLpHKL35gPz+LAfCphCW5rwpR
lvDhS+Pi/1KCBJxLHMv+V/WrckDRgHFnAhDaBZ+2vI/s09rKDnpjcTzV7x22kL0b
XIJCEEE8JZ4AXIZ+IcB6LA==
-----END PRIVATE KEY-----`) -----END PRIVATE KEY-----`)
// openssl req -newkey rsa:2048 \ // openssl req -newkey rsa:2048 \
@ -109,8 +127,6 @@ ajIPbTY+Fe9OTOFTN48ujXNn
-----END PRIVATE KEY-----`) -----END PRIVATE KEY-----`)
func Test_ServiceTCPHealthChecker_Check(t *testing.T) { func Test_ServiceTCPHealthChecker_Check(t *testing.T) {
t.Parallel()
testCases := []struct { testCases := []struct {
desc string desc string
server *sequencedTcpServer server *sequencedTcpServer
@ -192,10 +208,12 @@ func Test_ServiceTCPHealthChecker_Check(t *testing.T) {
desc: "healthy server with TLS certificate", desc: "healthy server with TLS certificate",
server: newTCPServer(t, server: newTCPServer(t,
true, true,
tcpMockSequence{accept: true}, tcpMockSequence{accept: true, payloadIn: "request", payloadOut: "response"},
tcpMockSequence{accept: true}, tcpMockSequence{accept: true, payloadIn: "request", payloadOut: "response"},
), ),
config: &dynamic.TCPServerHealthCheck{ config: &dynamic.TCPServerHealthCheck{
Payload: "request",
Expected: "response",
Interval: ptypes.Duration(time.Millisecond * 100), Interval: ptypes.Duration(time.Millisecond * 100),
Timeout: ptypes.Duration(time.Millisecond * 99), Timeout: ptypes.Duration(time.Millisecond * 99),
TLS: true, TLS: true,
@ -209,9 +227,8 @@ func Test_ServiceTCPHealthChecker_Check(t *testing.T) {
for _, test := range testCases { for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() c, cancel := context.WithCancel(context.Background())
ctx := log.Logger.WithContext(c)
ctx, cancel := context.WithCancel(context.Background())
defer t.Cleanup(cancel) defer t.Cleanup(cancel)
test.server.Start(t) test.server.Start(t)
@ -227,6 +244,7 @@ func Test_ServiceTCPHealthChecker_Check(t *testing.T) {
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": { dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {
TLS: &dynamic.TLSClientConfig{ TLS: &dynamic.TLSClientConfig{
InsecureSkipVerify: true, InsecureSkipVerify: true,
ServerName: "example.com",
}, },
}}) }})
service := NewServiceTCPHealthChecker(dialerManager, &MetricsMock{gauge}, test.config, lb, serviceInfo, targets, "serviceName") service := NewServiceTCPHealthChecker(dialerManager, &MetricsMock{gauge}, test.config, lb, serviceInfo, targets, "serviceName")
@ -263,7 +281,9 @@ type sequencedTcpServer struct {
} }
func newTCPServer(t *testing.T, tlsEnabled bool, statusSequence ...tcpMockSequence) *sequencedTcpServer { func newTCPServer(t *testing.T, tlsEnabled bool, statusSequence ...tcpMockSequence) *sequencedTcpServer {
listener, err := net.ListenTCP("tcp", net.TCPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:0"))) addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
require.NoError(t, err)
listener, err := net.ListenTCP("tcp", addr)
require.NoError(t, err) require.NoError(t, err)
tcpAddr, ok := listener.Addr().(*net.TCPAddr) tcpAddr, ok := listener.Addr().(*net.TCPAddr)
@ -287,57 +307,76 @@ func (s *sequencedTcpServer) Start(t *testing.T) {
t.Helper() t.Helper()
go func() { go func() {
var listener net.Listener
for _, seq := range s.StatusSequence { for _, seq := range s.StatusSequence {
<-s.release <-s.release
if listener != nil {
listener.Close()
}
if !seq.accept { if !seq.accept {
continue continue
} }
var listener net.Listener lis, err := net.ListenTCP("tcp", s.Addr)
listener, err := net.ListenTCP("tcp", s.Addr)
require.NoError(t, err) require.NoError(t, err)
listener = lis
if s.TLS { if s.TLS {
cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey) cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
require.NoError(t, err) require.NoError(t, err)
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
require.NoError(t, err)
certpool := x509.NewCertPool()
certpool.AddCert(x509Cert)
listener = tls.NewListener( listener = tls.NewListener(
listener, listener,
&tls.Config{ &tls.Config{
RootCAs: certpool,
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
ServerName: "example.com",
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS12,
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: certpool,
}, },
) )
} }
conn, err := listener.Accept() conn, err := listener.Accept()
require.NoError(t, err) require.NoError(t, err)
t.Cleanup(func() {
listener.Close() _ = conn.Close()
})
if seq.payloadIn == "" { if seq.payloadIn == "" {
conn.Close()
continue continue
} }
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) buf := make([]byte, len(seq.payloadIn))
buf := make([]byte, 1024) n, err := conn.Read(buf)
n, _ := conn.Read(buf) require.NoError(t, err)
recv := strings.TrimSpace(string(buf[:n])) recv := strings.TrimSpace(string(buf[:n]))
switch recv { switch recv {
case seq.payloadIn: case seq.payloadIn:
_, _ = conn.Write([]byte(seq.payloadOut)) if _, err := conn.Write([]byte(seq.payloadOut)); err != nil {
t.Errorf("failed to write payload: %v", err)
}
default: default:
_, _ = conn.Write([]byte("FAULT\n")) if _, err := conn.Write([]byte("FAULT\n")); err != nil {
t.Errorf("failed to write payload: %v", err)
}
}
} }
defer conn.Close() defer close(s.release)
}
close(s.release)
}() }()
} }