From d74fd449e1e94a63eac8d3695aac241eebb5a7a0 Mon Sep 17 00:00:00 2001 From: Matej Vasek Date: Fri, 9 Jun 2023 00:39:59 +0200 Subject: [PATCH] feat: DialContext() more better error handling Translate some socat errors into Go errors instead of just putting stderr into an error.New(). Signed-off-by: Matej Vasek --- pkg/k8s/dialer.go | 68 +++++++++++++++++++++++++++++++++++++++++- pkg/k8s/dialer_test.go | 15 ++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/pkg/k8s/dialer.go b/pkg/k8s/dialer.go index e6799c802..cd9b946b0 100644 --- a/pkg/k8s/dialer.go +++ b/pkg/k8s/dialer.go @@ -8,9 +8,12 @@ import ( "fmt" "io" "net" + "os" "regexp" + "strconv" "sync" "sync/atomic" + "syscall" "time" coreV1 "k8s.io/api/core/v1" @@ -78,7 +81,13 @@ func (c *contextDialer) DialContext(ctx context.Context, network string, addr st err := c.exec(addr, ctrStdin, ctrStdout, ctrStderr) if err != nil { - err = fmt.Errorf("failed to exec in pod: %w (stderr: %q)", err, stderrBuff.String()) + stderrStr := stderrBuff.String() + socatErr := tryParseSocatError(network, addr, stderrStr) + if socatErr != nil { + err = fmt.Errorf("socat error: %w", socatErr) + } else { + err = fmt.Errorf("failed to exec in pod: %w (stderr: %q)", err, stderrStr) + } } _ = conn.closeWithError(err) connectFailure <- err @@ -110,6 +119,63 @@ func detectConnSuccess(connectSuccess chan struct{}) io.Writer { return pw } +var ( + connectionRefusedErrorRE = regexp.MustCompile(`E connect\(\d+, AF=\d+ (?P[\[\]0-9.:a-z]+), \d+\): Connection refused`) + nameResolutionErrorRE = regexp.MustCompile(`E getaddrinfo\("(?P[a-zA-z-.0-9]+)",.*\): Name does not resolve`) +) + +// tries to detect common errors from `socat` stderr +func tryParseSocatError(network, address, stderr string) error { + groups := nameResolutionErrorRE.FindStringSubmatch(stderr) + if groups != nil { + var name string + if len(groups) > 1 { + name = groups[1] + } + return &net.OpError{ + Op: "dial", + Net: network, + Source: nil, + Addr: nil, + Err: &net.DNSError{ + Err: "no such host", + Name: name, + IsNotFound: true, + }, + } + } + groups = connectionRefusedErrorRE.FindStringSubmatch(stderr) + if groups != nil { + var ( + addr net.IP + port int + zone string + ) + if len(groups) > 1 { + h, p, err := net.SplitHostPort(groups[1]) + if err == nil { + addr = net.ParseIP(h) + p, _ := strconv.ParseInt(p, 10, 16) + port = int(p) + } + } + return &net.OpError{ + Op: "dial", + Net: network, + Addr: &net.TCPAddr{ + IP: addr, + Port: port, + Zone: zone, + }, + Err: &os.SyscallError{ + Syscall: "connect", + Err: syscall.ECONNREFUSED, + }, + } + } + return nil +} + func (c *contextDialer) Close() error { // closing the channel will cause stdin of the attached container to return EOF // as a result the pod exits -- it transits to Completed state diff --git a/pkg/k8s/dialer_test.go b/pkg/k8s/dialer_test.go index df19f7412..aaef149d3 100644 --- a/pkg/k8s/dialer_test.go +++ b/pkg/k8s/dialer_test.go @@ -167,13 +167,22 @@ func TestDialUnreachable(t *testing.T) { t.Cleanup(func() { dialer.Close() }) - + _, err = dialer.DialContext(ctx, "tcp", "does-not.exists.svc:80") if err == nil { t.Error("error was expected but got nil") return } - if !strings.Contains(err.Error(), "not resolve") { - t.Errorf("error %q doesn't containe expected sub-string: ", err.Error()) + if !strings.Contains(err.Error(), "no such host") { + t.Errorf("error %q doesn't containe expected substring: ", err.Error()) + } + + _, err = dialer.DialContext(ctx, "tcp", "localhost:80") + if err == nil { + t.Error("error was expected but got nil") + return + } + if !strings.Contains(err.Error(), "connection refused") { + t.Errorf("error %q doesn't containe expected substring: ", err.Error()) } }