diff --git a/plugin/clouddns/README.md b/plugin/clouddns/README.md index 6a1e92e2e..506ecbabc 100644 --- a/plugin/clouddns/README.md +++ b/plugin/clouddns/README.md @@ -21,6 +21,7 @@ the hosted zone. clouddns [ZONE:PROJECT_ID:HOSTED_ZONE_NAME...] { credentials [FILENAME] fallthrough [ZONES...] + refresh DURATION } ~~~ @@ -45,6 +46,12 @@ clouddns [ZONE:PROJECT_ID:HOSTED_ZONE_NAME...] { authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only queries for those zones will be subject to fallthrough. +* `refresh` can be used to control how long between record retrievals from Cloud DNS. It requires + a duration string as a parameter to specify the duration between update cycles. Each update + cycle may result in many Cloud DNS API calls depending on how many domains use this plugin and + how many records are in each. Adjusting the update frequency may help reduce the potential of + API throttling by Google Cloud. If not provided the default refresh is 1 minute. + ## Examples Enable clouddns with implicit GCP credentials and resolve CNAMEs via 10.0.0.1: @@ -73,3 +80,13 @@ Enable clouddns with multiple hosted zones with the same domain: clouddns example.org.:gcp-example-project:example-zone example.com.:gcp-example-project:other-example-zone } ~~~ + +Enable clouddns and refresh records every 3 minutes: + +~~~ txt +example.org { + clouddns example.org.:gcp-example-project:example-zone { + refresh 3m + } +} +~~~ diff --git a/plugin/clouddns/clouddns.go b/plugin/clouddns/clouddns.go index 08b842848..9be7fed54 100644 --- a/plugin/clouddns/clouddns.go +++ b/plugin/clouddns/clouddns.go @@ -28,6 +28,7 @@ type CloudDNS struct { zoneNames []string client gcpDNS upstream *upstream.Upstream + refresh time.Duration zMu sync.RWMutex zones zones @@ -46,8 +47,9 @@ type zones map[string][]*zone // string of project name and hosted zone name lists as its values, validates // that each domain name/zone id pair does exist, and returns a new *CloudDNS. // In addition to this, upstream is passed for doing recursive queries against CNAMEs. +// refresh sets how often the zones are refreshed from Cloud DNS. // Returns error if it cannot verify any given domain name/zone id pair. -func New(_ctx context.Context, c gcpDNS, keys map[string][]string, up *upstream.Upstream) (*CloudDNS, error) { +func New(_ctx context.Context, c gcpDNS, keys map[string][]string, up *upstream.Upstream, refresh time.Duration) (*CloudDNS, error) { zones := make(map[string][]*zone, len(keys)) zoneNames := make([]string, 0, len(keys)) for dnsName, hostedZoneDetails := range keys { @@ -72,6 +74,7 @@ func New(_ctx context.Context, c gcpDNS, keys map[string][]string, up *upstream. zoneNames: zoneNames, zones: zones, upstream: up, + refresh: refresh, }, nil } @@ -82,11 +85,10 @@ func (h *CloudDNS) Run(ctx context.Context) error { return err } go func() { - delay := 1 * time.Minute - timer := time.NewTimer(delay) + timer := time.NewTimer(h.refresh) defer timer.Stop() for { - timer.Reset(delay) + timer.Reset(h.refresh) select { case <-ctx.Done(): log.Debugf("Breaking out of CloudDNS update loop for %v: %v", h.zoneNames, ctx.Err()) diff --git a/plugin/clouddns/clouddns_test.go b/plugin/clouddns/clouddns_test.go index d42354415..2673a259c 100644 --- a/plugin/clouddns/clouddns_test.go +++ b/plugin/clouddns/clouddns_test.go @@ -6,6 +6,7 @@ import ( "reflect" "sync" "testing" + "time" "github.com/coredns/coredns/plugin/file" "github.com/coredns/coredns/plugin/pkg/dnstest" @@ -134,7 +135,7 @@ func (c fakeGCPClient) listRRSets(_ctx context.Context, projectName, hostedZoneN func TestCloudDNS(t *testing.T) { ctx := context.Background() - r, err := New(ctx, fakeGCPClient{}, map[string][]string{"bad.": {"bad-project:bad-zone"}}, &upstream.Upstream{}) + r, err := New(ctx, fakeGCPClient{}, map[string][]string{"bad.": {"bad-project:bad-zone"}}, &upstream.Upstream{}, time.Minute) if err != nil { t.Fatalf("Failed to create Cloud DNS: %v", err) } @@ -142,7 +143,7 @@ func TestCloudDNS(t *testing.T) { t.Fatalf("Expected errors for zone bad.") } - r, err = New(ctx, fakeGCPClient{}, map[string][]string{"org.": {"sample-project-1:sample-zone-2", "sample-project-1:sample-zone-1"}, "gov.": {"sample-project-1:sample-zone-2", "sample-project-1:sample-zone-1"}}, &upstream.Upstream{}) + r, err = New(ctx, fakeGCPClient{}, map[string][]string{"org.": {"sample-project-1:sample-zone-2", "sample-project-1:sample-zone-1"}, "gov.": {"sample-project-1:sample-zone-2", "sample-project-1:sample-zone-1"}}, &upstream.Upstream{}, time.Minute) if err != nil { t.Fatalf("Failed to create Cloud DNS: %v", err) } @@ -339,6 +340,7 @@ func TestCloudDNSConcurrentServeDNS(t *testing.T) { "org.": {"sample-project-1:sample-zone-2", "sample-project-1:sample-zone-1"}, }, &upstream.Upstream{}, + time.Minute, ) if err != nil { t.Fatalf("Failed to create Cloud DNS: %v", err) @@ -406,7 +408,8 @@ func TestCloudDNSConcurrentLookupNameCache(t *testing.T) { map[string][]string{ "org.": {"sample-project-1:sample-zone-2", "sample-project-1:sample-zone-1"}, }, - &upstream.Upstream{}) + &upstream.Upstream{}, + time.Minute) if err != nil { t.Fatalf("Failed to create Cloud DNS: %v", err) } diff --git a/plugin/clouddns/setup.go b/plugin/clouddns/setup.go index fe2035c3e..9306134ac 100644 --- a/plugin/clouddns/setup.go +++ b/plugin/clouddns/setup.go @@ -5,7 +5,9 @@ import ( "encoding/json" "fmt" "os" + "strconv" "strings" + "time" "github.com/coredns/caddy" "github.com/coredns/coredns/core/dnsserver" @@ -64,6 +66,7 @@ func setup(c *caddy.Controller) error { } var opt option.ClientOption + refresh := time.Duration(1) * time.Minute // default update frequency to 1 minute for c.NextBlock() { switch c.Val() { case "upstream": @@ -79,6 +82,22 @@ func setup(c *caddy.Controller) error { opt = option.WithAuthCredentialsFile(credType, c.Val()) case "fallthrough": fall.SetZonesFromArgs(c.RemainingArgs()) + case "refresh": + if !c.NextArg() { + return plugin.Error("clouddns", c.ArgErr()) + } + refreshStr := c.Val() + _, err := strconv.Atoi(refreshStr) + if err == nil { + refreshStr += "s" + } + refresh, err = time.ParseDuration(refreshStr) + if err != nil { + return plugin.Error("clouddns", c.Errf("unable to parse duration: %v", err)) + } + if refresh <= 0 { + return plugin.Error("clouddns", c.Errf("refresh interval must be greater than 0: %q", refreshStr)) + } default: return plugin.Error("clouddns", c.Errf("unknown property %q", c.Val())) } @@ -91,7 +110,7 @@ func setup(c *caddy.Controller) error { return err } - h, err := New(ctx, client, keys, up) + h, err := New(ctx, client, keys, up, refresh) if err != nil { cancel() return plugin.Error("clouddns", c.Errf("failed to create plugin: %v", err)) diff --git a/plugin/clouddns/setup_test.go b/plugin/clouddns/setup_test.go index 1880395be..905347137 100644 --- a/plugin/clouddns/setup_test.go +++ b/plugin/clouddns/setup_test.go @@ -55,6 +55,24 @@ func TestSetupCloudDNS(t *testing.T) { }`, false}, {`clouddns example.org.:example-project:zone-name { credentials +}`, true}, + {`clouddns example.org.:example-project:zone-name { + refresh 3m +}`, false}, + {`clouddns example.org.:example-project:zone-name { + refresh 30 +}`, false}, + {`clouddns example.org.:example-project:zone-name { + refresh +}`, true}, + {`clouddns example.org.:example-project:zone-name { + refresh abc +}`, true}, + {`clouddns example.org.:example-project:zone-name { + refresh 0 +}`, true}, + {`clouddns example.org.:example-project:zone-name { + refresh -5m }`, true}, {`clouddns example.org.:example-project:zone-name example.org.:example-project:zone-name { }`, true},