Skip to content

Commit 0fae060

Browse files
Hackatoshvflaux
andauthored
fix(cloudflare): improve handling of rate limiting errors (kubernetes-sigs#5524)
* fix cloudflare * factorize error conversion * only log if not a soft error * add comment for workaround * Remove debug print Co-authored-by: vflaux <[email protected]> * Use assert Co-authored-by: vflaux <[email protected]> * Improve error check Co-authored-by: vflaux <[email protected]> * fix lint --------- Co-authored-by: vflaux <[email protected]>
1 parent 0f0e05e commit 0fae060

File tree

2 files changed

+36
-24
lines changed

2 files changed

+36
-24
lines changed

provider/cloudflare/cloudflare.go

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,23 @@ func getCreateDNSRecordParam(cfc cloudFlareChange) cloudflare.CreateDNSRecordPar
271271
}
272272
}
273273

274+
func convertCloudflareError(err error) error {
275+
var apiErr *cloudflare.Error
276+
if errors.As(err, &apiErr) {
277+
if apiErr.ClientRateLimited() || apiErr.StatusCode >= http.StatusInternalServerError {
278+
// Handle rate limit error as a soft error
279+
return provider.NewSoftError(err)
280+
}
281+
}
282+
// This is a workaround because Cloudflare library does not return a specific error type for rate limit exceeded.
283+
// See https://github.com/cloudflare/cloudflare-go/issues/4155 and https://github.com/kubernetes-sigs/external-dns/pull/5524
284+
// This workaround can be removed once Cloudflare library returns a specific error type.
285+
if strings.Contains(err.Error(), "exceeded available rate limit retries") {
286+
return provider.NewSoftError(err)
287+
}
288+
return err
289+
}
290+
274291
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
275292
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, regionKey string, customHostnamesConfig CustomHostnamesConfig, dnsRecordsConfig DNSRecordsConfig) (*CloudFlareProvider, error) {
276293
// initialize via chosen auth method and returns new API object
@@ -335,14 +352,7 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
335352

336353
zonesResponse, err := p.Client.ListZonesContext(ctx)
337354
if err != nil {
338-
var apiErr *cloudflare.Error
339-
if errors.As(err, &apiErr) {
340-
if apiErr.ClientRateLimited() || apiErr.StatusCode >= http.StatusInternalServerError {
341-
// Handle rate limit error as a soft error
342-
return nil, provider.NewSoftError(err)
343-
}
344-
}
345-
return nil, err
355+
return nil, convertCloudflareError(err)
346356
}
347357

348358
for _, zone := range zonesResponse.Result {
@@ -753,14 +763,7 @@ func (p *CloudFlareProvider) listDNSRecordsWithAutoPagination(ctx context.Contex
753763
for {
754764
pageRecords, resultInfo, err := p.Client.ListDNSRecords(ctx, cloudflare.ZoneIdentifier(zoneID), params)
755765
if err != nil {
756-
var apiErr *cloudflare.Error
757-
if errors.As(err, &apiErr) {
758-
if apiErr.ClientRateLimited() || apiErr.StatusCode >= http.StatusInternalServerError {
759-
// Handle rate limit error as a soft error
760-
return nil, provider.NewSoftError(err)
761-
}
762-
}
763-
return nil, err
766+
return nil, convertCloudflareError(err)
764767
}
765768

766769
for _, r := range pageRecords {
@@ -788,15 +791,11 @@ func (p *CloudFlareProvider) listCustomHostnamesWithPagination(ctx context.Conte
788791
for {
789792
pageCustomHostnameListResponse, result, err := p.Client.CustomHostnames(ctx, zoneID, resultInfo.Page, cloudflare.CustomHostname{})
790793
if err != nil {
791-
var apiErr *cloudflare.Error
792-
if errors.As(err, &apiErr) {
793-
if apiErr.ClientRateLimited() || apiErr.StatusCode >= http.StatusInternalServerError {
794-
// Handle rate limit error as a soft error
795-
return nil, provider.NewSoftError(err)
796-
}
794+
convertedError := convertCloudflareError(err)
795+
if !errors.Is(convertedError, provider.SoftError) {
796+
log.Errorf("zone %q failed to fetch custom hostnames. Please check if \"Cloudflare for SaaS\" is enabled and API key permissions, %v", zoneID, err)
797797
}
798-
log.Errorf("zone %q failed to fetch custom hostnames. Please check if \"Cloudflare for SaaS\" is enabled and API key permissions, %v", zoneID, err)
799-
return nil, err
798+
return nil, convertedError
800799
}
801800
for _, ch := range pageCustomHostnameListResponse {
802801
chs[newCustomHostnameIndex(ch)] = ch

provider/cloudflare/cloudflare_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,19 @@ func TestCloudflareListZonesRateLimited(t *testing.T) {
955955
}
956956
}
957957

958+
func TestCloudflareListZonesRateLimitedStringError(t *testing.T) {
959+
// Create a mock client that returns a rate limit error
960+
client := NewMockCloudFlareClient()
961+
client.listZonesContextError = errors.New("exceeded available rate limit retries")
962+
p := &CloudFlareProvider{Client: client}
963+
964+
// Call the Zones function
965+
_, err := p.Zones(context.Background())
966+
967+
// Assert that a soft error was returned
968+
assert.ErrorIs(t, err, provider.SoftError, "expected a rate limit error")
969+
}
970+
958971
func TestCloudflareListZoneInternalErrors(t *testing.T) {
959972
// Create a mock client that returns a internal server error
960973
client := NewMockCloudFlareClient()

0 commit comments

Comments
 (0)