Skip to content

Commit

Permalink
fix(godaddy): Handle missing Retry-After header gracefully
Browse files Browse the repository at this point in the history
  • Loading branch information
alexstojda committed Dec 11, 2024
1 parent fe2924b commit c4dfbe4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 1 deletion.
7 changes: 6 additions & 1 deletion provider/godaddy/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions provider/godaddy/client_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit c4dfbe4

Please sign in to comment.