diff --git a/provider/godaddy/client.go b/provider/godaddy/client.go index 6afc48a249..79f566f186 100644 --- a/provider/godaddy/client.go +++ b/provider/godaddy/client.go @@ -28,6 +28,7 @@ import ( "strconv" "time" + log "github.com/sirupsen/logrus" "golang.org/x/time/rate" "sigs.k8s.io/external-dns/pkg/apis/externaldns" @@ -230,7 +231,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) { resp, err := c.Client.Do(req) // In case of several clients behind NAT we still can hit rate limit for i := 1; i < 3 && err == nil && resp.StatusCode == 429; i++ { - retryAfter, _ := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 0) + retryAfter, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 0) + if err != nil { + log.Error("Rate-limited response did not contain a valid Retry-After header, quota likely exceeded") + break + } jitter := rand.Int63n(retryAfter) retryAfterSec := retryAfter + jitter/2 diff --git a/provider/godaddy/client_test.go b/provider/godaddy/client_test.go new file mode 100644 index 0000000000..0a3789d6a2 --- /dev/null +++ b/provider/godaddy/client_test.go @@ -0,0 +1,56 @@ +package godaddy + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "golang.org/x/time/rate" +) + +// Tests that +func TestClient_DoWhenQuotaExceeded(t *testing.T) { + assert := assert.New(t) + + // Mock server to return 429 with a JSON payload + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusTooManyRequests) + _, err := w.Write([]byte(`{"code": "QUOTA_EXCEEDED", "message": "rate limit exceeded"}`)) + if err != nil { + t.Fatalf("Failed to write response: %v", err) + } + })) + defer mockServer.Close() + + client := Client{ + APIKey: "", + APISecret: "", + APIEndPoint: mockServer.URL, + Client: &http.Client{}, + // Add one token every second + Ratelimiter: rate.NewLimiter(rate.Every(time.Second), 60), + Timeout: DefaultTimeout, + } + + req, err := client.NewRequest("GET", "/v1/domains/example.net/records", nil, false) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + resp, err := client.Do(req) + assert.Nil(err, "A CODE_EXCEEDED response should not return an error") + assert.Equal(http.StatusTooManyRequests, resp.StatusCode, "Expected a 429 response") + + respContents := GDErrorResponse{} + err = client.UnmarshalResponse(resp, &respContents) + if assert.NotNil(err) { + var apiErr *APIError + errors.As(err, &apiErr) + assert.Equal("QUOTA_EXCEEDED", apiErr.Code) + assert.Equal("rate limit exceeded", apiErr.Message) + } +}