fix: retry on download errors

to avoid failing because of issues like connection reset
This commit is contained in:
Carlos Sanchez 2024-06-30 12:54:06 +02:00
parent 347cd5e4c9
commit 7b9163b6c9
2 changed files with 67 additions and 13 deletions

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"net"
"net/http" "net/http"
"os" "os"
"time" "time"
@ -74,6 +75,24 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers
} }
resp, err := r.c.Get(url) resp, err := r.c.Get(url)
// retry on transient errors, ie. connection reset by peer
if err != nil {
if opErr, ok := err.(*net.OpError); ok {
if r.debug {
log.Printf("failed downloading schema at %s due to network error, retrying: %s", url, opErr)
}
time.Sleep(1 * time.Second)
resp, err = r.c.Get(url)
}
}
// retry on server errors
if resp != nil && resp.StatusCode >= 500 {
if r.debug {
log.Printf("failed downloading schema at %s due to server error, retrying: %d", url, resp.StatusCode)
}
time.Sleep(1 * time.Second)
resp, err = r.c.Get(url)
}
if err != nil { if err != nil {
msg := fmt.Sprintf("failed downloading schema at %s: %s", url, err) msg := fmt.Sprintf("failed downloading schema at %s: %s", url, err)
if r.debug { if r.debug {

View file

@ -2,24 +2,29 @@ package registry
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
) )
type mockHTTPGetter struct { type mockHTTPGetter struct {
httpGet func(string) (*http.Response, error) callNumber int
httpGet func(mockHTTPGetter, string) (*http.Response, error)
} }
func newMockHTTPGetter(f func(string) (*http.Response, error)) *mockHTTPGetter { func newMockHTTPGetter(f func(mockHTTPGetter, string) (*http.Response, error)) *mockHTTPGetter {
return &mockHTTPGetter{ return &mockHTTPGetter{
httpGet: f, callNumber: 0,
httpGet: f,
} }
} }
func (m mockHTTPGetter) Get(url string) (resp *http.Response, err error) { func (m *mockHTTPGetter) Get(url string) (resp *http.Response, err error) {
return m.httpGet(url) m.callNumber = m.callNumber + 1
return m.httpGet(*m, url)
} }
func TestDownloadSchema(t *testing.T) { func TestDownloadSchema(t *testing.T) {
@ -33,21 +38,28 @@ func TestDownloadSchema(t *testing.T) {
expectErr error expectErr error
}{ }{
{ {
"error when downloading", "retry connection reset by peer",
newMockHTTPGetter(func(url string) (resp *http.Response, err error) { newMockHTTPGetter(func(c mockHTTPGetter, url string) (resp *http.Response, err error) {
return nil, fmt.Errorf("failed downloading from registry") if c.callNumber == 1 {
return nil, &net.OpError{Err: errors.New("connection reset by peer")}
} else {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("http response mock body")),
}, nil
}
}), }),
"http://kubernetesjson.dev", "http://kubernetesjson.dev",
true, true,
"Deployment", "Deployment",
"v1", "v1",
"1.18.0", "1.18.0",
[]byte("http response mock body"),
nil, nil,
fmt.Errorf("failed downloading schema at http://kubernetesjson.dev: failed downloading from registry"),
}, },
{ {
"getting 404", "getting 404",
newMockHTTPGetter(func(url string) (resp *http.Response, err error) { newMockHTTPGetter(func(c mockHTTPGetter, url string) (resp *http.Response, err error) {
return &http.Response{ return &http.Response{
StatusCode: http.StatusNotFound, StatusCode: http.StatusNotFound,
Body: io.NopCloser(strings.NewReader("http response mock body")), Body: io.NopCloser(strings.NewReader("http response mock body")),
@ -62,8 +74,8 @@ func TestDownloadSchema(t *testing.T) {
fmt.Errorf("could not find schema at http://kubernetesjson.dev"), fmt.Errorf("could not find schema at http://kubernetesjson.dev"),
}, },
{ {
"getting 503", "getting 500",
newMockHTTPGetter(func(url string) (resp *http.Response, err error) { newMockHTTPGetter(func(c mockHTTPGetter, url string) (resp *http.Response, err error) {
return &http.Response{ return &http.Response{
StatusCode: http.StatusServiceUnavailable, StatusCode: http.StatusServiceUnavailable,
Body: io.NopCloser(strings.NewReader("http response mock body")), Body: io.NopCloser(strings.NewReader("http response mock body")),
@ -77,9 +89,32 @@ func TestDownloadSchema(t *testing.T) {
nil, nil,
fmt.Errorf("error while downloading schema at http://kubernetesjson.dev - received HTTP status 503"), fmt.Errorf("error while downloading schema at http://kubernetesjson.dev - received HTTP status 503"),
}, },
{
"retry 503",
newMockHTTPGetter(func(c mockHTTPGetter, url string) (resp *http.Response, err error) {
if c.callNumber == 1 {
return &http.Response{
StatusCode: http.StatusServiceUnavailable,
Body: io.NopCloser(strings.NewReader("503 http response mock body")),
}, nil
} else {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("http response mock body")),
}, nil
}
}),
"http://kubernetesjson.dev",
true,
"Deployment",
"v1",
"1.18.0",
[]byte("http response mock body"),
nil,
},
{ {
"200", "200",
newMockHTTPGetter(func(url string) (resp *http.Response, err error) { newMockHTTPGetter(func(c mockHTTPGetter, url string) (resp *http.Response, err error) {
return &http.Response{ return &http.Response{
StatusCode: http.StatusOK, StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("http response mock body")), Body: io.NopCloser(strings.NewReader("http response mock body")),