From 0de789f3812b78109934855762f50b1bd42551da Mon Sep 17 00:00:00 2001 From: fabienmazieres <31537139+fabienmazieres@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:18:42 +0100 Subject: [PATCH] ORACLE: Abide by 429-style rate limits, fix Nameserver update bug (#3090) Co-authored-by: Tom Limoncelli --- documentation/provider/oracle.md | 12 +++++ providers/oracle/auditrecords.go | 13 +++++- providers/oracle/oracleProvider.go | 71 +++++++++++++++++++++++++++--- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/documentation/provider/oracle.md b/documentation/provider/oracle.md index 62ea7782d..d9fc99229 100644 --- a/documentation/provider/oracle.md +++ b/documentation/provider/oracle.md @@ -25,9 +25,11 @@ Example: {% endcode %} ## Metadata + This provider does not recognize any special metadata fields unique to Oracle Cloud. ## Usage + An example configuration: {% code title="dnsconfig.js" %} @@ -42,3 +44,13 @@ D("example.com", REG_NONE, DnsProvider(DSP_ORACLE), END); ``` {% endcode %} + +## Notes for developers + +Integration does not have the capability to set the TTL set differently when Oracle is the provider being tested. +You will see an error message behind displayed, such as below, but it can be safely ignored. + +```Text +=== RUN TestDNSProviders/example.co.uk/Clean_Slate:Empty +WARNING: Oracle Cloud forces TTL=86400 for NS records. Ignoring configured TTL of 300 for ns1.p201.dns.oraclecloud.net. +``` diff --git a/providers/oracle/auditrecords.go b/providers/oracle/auditrecords.go index 629ff55e4..1f24276af 100644 --- a/providers/oracle/auditrecords.go +++ b/providers/oracle/auditrecords.go @@ -1,10 +1,19 @@ package oracle -import "github.com/StackExchange/dnscontrol/v4/models" +import ( + "github.com/StackExchange/dnscontrol/v4/models" + "github.com/StackExchange/dnscontrol/v4/pkg/rejectif" +) // AuditRecords returns a list of errors corresponding to the records // that aren't supported by this provider. If all records are // supported, an empty list is returned. func AuditRecords(records []*models.RecordConfig) []error { - return nil + a := rejectif.Auditor{} + + a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2024-08-21 + a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2024-08-21 + a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2024-08-21 + + return a.Audit(records) } diff --git a/providers/oracle/oracleProvider.go b/providers/oracle/oracleProvider.go index 1bcca0e5e..4e303c3be 100644 --- a/providers/oracle/oracleProvider.go +++ b/providers/oracle/oracleProvider.go @@ -3,6 +3,7 @@ package oracle import ( "context" "encoding/json" + "net/http" "strings" "time" @@ -75,10 +76,15 @@ func (o *oracleProvider) ListZones() ([]string, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() + waitTime = 1 +retry: listResp, err := o.client.ListZones(ctx, dns.ListZonesRequest{ CompartmentId: &o.compartment, }) if err != nil { + if pauseAndRetry(listResp.HTTPResponse()) { + goto retry + } return nil, err } @@ -94,6 +100,8 @@ func (o *oracleProvider) EnsureZoneExists(domain string) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() + waitTime = 1 +retryFirstGetZone: getResp, err := o.client.GetZone(ctx, dns.GetZoneRequest{ ZoneNameOrId: &domain, CompartmentId: &o.compartment, @@ -101,11 +109,18 @@ func (o *oracleProvider) EnsureZoneExists(domain string) error { if err == nil { return nil } - if getResp.RawResponse.StatusCode != 404 { - return err + if err != nil { + if pauseAndRetry(getResp.HTTPResponse()) { + goto retryFirstGetZone + } + if getResp.RawResponse.StatusCode != 404 { + return err + } } - _, err = o.client.CreateZone(ctx, dns.CreateZoneRequest{ + waitTime = 1 +retryCreate: + createResp, err := o.client.CreateZone(ctx, dns.CreateZoneRequest{ CreateZoneDetails: dns.CreateZoneDetails{ CompartmentId: &o.compartment, Name: &domain, @@ -113,9 +128,14 @@ func (o *oracleProvider) EnsureZoneExists(domain string) error { }, }) if err != nil { + if pauseAndRetry(createResp.HTTPResponse()) { + goto retryCreate + } return err } + waitTime = 1 +retrySecondGetZone: // poll until the zone is ready pollUntilAvailable := func(r common.OCIOperationResponse) bool { if converted, ok := r.Response.(dns.GetZoneResponse); ok { @@ -123,11 +143,16 @@ func (o *oracleProvider) EnsureZoneExists(domain string) error { } return true } - _, err = o.client.GetZone(ctx, dns.GetZoneRequest{ + getResp, err = o.client.GetZone(ctx, dns.GetZoneRequest{ ZoneNameOrId: &domain, CompartmentId: &o.compartment, RequestMetadata: helpers.GetRequestMetadataWithCustomizedRetryPolicy(pollUntilAvailable), }) + if err != nil { + if pauseAndRetry(createResp.HTTPResponse()) { + goto retrySecondGetZone + } + } return err } @@ -136,11 +161,16 @@ func (o *oracleProvider) GetNameservers(domain string) ([]*models.Nameserver, er ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() + waitTime = 1 +retry: getResp, err := o.client.GetZone(ctx, dns.GetZoneRequest{ ZoneNameOrId: &domain, CompartmentId: &o.compartment, }) if err != nil { + if pauseAndRetry(getResp.HTTPResponse()) { + goto retry + } return nil, err } @@ -149,7 +179,7 @@ func (o *oracleProvider) GetNameservers(domain string) ([]*models.Nameserver, er nss[i] = *ns.Hostname } - return models.ToNameservers(nss) + return models.ToNameserversStripTD(nss) } func (o *oracleProvider) GetZoneRecords(zone string, meta map[string]string) (models.Records, error) { @@ -164,8 +194,13 @@ func (o *oracleProvider) GetZoneRecords(zone string, meta map[string]string) (mo } for { + waitTime = 1 + retry: getResp, err := o.client.GetZoneRecords(ctx, request) if err != nil { + if pauseAndRetry(getResp.HTTPResponse()) { + goto retry + } return nil, err } @@ -309,8 +344,14 @@ func (o *oracleProvider) patch(createRecords, deleteRecords models.Records, doma batchEnd = len(ops) } patchReq.Items = ops[batchStart:batchEnd] - _, err := o.client.PatchZoneRecords(ctx, patchReq) + + waitTime = 1 + retry: + response, err := o.client.PatchZoneRecords(ctx, patchReq) if err != nil { + if pauseAndRetry(response.HTTPResponse()) { + goto retry + } return err } } @@ -339,3 +380,21 @@ func convertToRecordOperation(rec *models.RecordConfig, op dns.RecordOperationOp Operation: op, } } + +// waitTime is the amount of time to sleep if a 429 is received. +// Must be reset before every query +var waitTime = 1 + +func pauseAndRetry(resp *http.Response) bool { + if resp.StatusCode == 429 { + waitTime = waitTime * 2 + if waitTime > 300 { + printer.Printf("Oracle: max wait for rate-limit reached.\n") + return false + } + printer.Printf("Oracle: API rate-limit hit, pause for %v seconds.\n", waitTime) + time.Sleep(time.Duration(waitTime+1) * time.Second) + return true + } + return false +}