mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	plugin/route53: make refresh frequency adjustable (#3083)
the current update frequency for the refresh loop in the route 53 plugin is hard-coded to 1 minute. aws rate-limits the number of api requests so less frequent record refreshes can help when reaching those limits depending upon your individual scenarios. this pull adds a configuration option to the route53 plugin to adjust the refresh frequency. thanks for getting my last pull released so quickly. this is the last local change that i have been running and would love to get it contributed back to the project. Signed-off-by: Matt Kulka <mkulka@parchment.com>
This commit is contained in:
		| @@ -18,6 +18,7 @@ route53 [ZONE:HOSTED_ZONE_ID...] { | |||||||
|     aws_access_key [AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY] |     aws_access_key [AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY] | ||||||
|     credentials PROFILE [FILENAME] |     credentials PROFILE [FILENAME] | ||||||
|     fallthrough [ZONES...] |     fallthrough [ZONES...] | ||||||
|  |     refresh DURATION | ||||||
| } | } | ||||||
| ~~~ | ~~~ | ||||||
|  |  | ||||||
| @@ -48,6 +49,14 @@ route53 [ZONE:HOSTED_ZONE_ID...] { | |||||||
| *   **ZONES** zones it should be authoritative for. If empty, the zones from the configuration | *   **ZONES** zones it should be authoritative for. If empty, the zones from the configuration | ||||||
|     block. |     block. | ||||||
|  |  | ||||||
|  | *   `refresh` can be used to control how long between record retrievals from Route 53. It requires | ||||||
|  |     a duration string as a parameter to specify the duration between update cycles. Each update | ||||||
|  |     cycle may result in many AWS 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 | ||||||
|  |     rate-limiting imposed by AWS. | ||||||
|  |  | ||||||
|  | *   **DURATION** A duration string. Defaults to `1m`. If units are unspecified, seconds are assumed. | ||||||
|  |  | ||||||
| ## Examples | ## Examples | ||||||
|  |  | ||||||
| Enable route53 with implicit AWS credentials and resolve CNAMEs via 10.0.0.1: | Enable route53 with implicit AWS credentials and resolve CNAMEs via 10.0.0.1: | ||||||
| @@ -86,3 +95,12 @@ Enable route53 with multiple hosted zones with the same domain: | |||||||
|     route53 example.org.:Z1Z2Z3Z4DZ5Z6Z7 example.org.:Z93A52145678156 |     route53 example.org.:Z1Z2Z3Z4DZ5Z6Z7 example.org.:Z93A52145678156 | ||||||
| } | } | ||||||
| ~~~ | ~~~ | ||||||
|  |  | ||||||
|  | Enable route53 and refresh records every 3 minutes | ||||||
|  | ~~~ txt | ||||||
|  | . { | ||||||
|  |     route53 example.org.:Z1Z2Z3Z4DZ5Z6Z7 { | ||||||
|  |       refresh 3m | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ~~~ | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ type Route53 struct { | |||||||
| 	zoneNames []string | 	zoneNames []string | ||||||
| 	client    route53iface.Route53API | 	client    route53iface.Route53API | ||||||
| 	upstream  *upstream.Upstream | 	upstream  *upstream.Upstream | ||||||
|  | 	refresh   time.Duration | ||||||
|  |  | ||||||
| 	zMu   sync.RWMutex | 	zMu   sync.RWMutex | ||||||
| 	zones zones | 	zones zones | ||||||
| @@ -49,7 +50,7 @@ type zones map[string][]*zone | |||||||
| // exist, and returns a new *Route53. In addition to this, upstream is passed | // exist, and returns a new *Route53. In addition to this, upstream is passed | ||||||
| // for doing recursive queries against CNAMEs. | // for doing recursive queries against CNAMEs. | ||||||
| // Returns error if it cannot verify any given domain name/zone id pair. | // Returns error if it cannot verify any given domain name/zone id pair. | ||||||
| func New(ctx context.Context, c route53iface.Route53API, keys map[string][]string, up *upstream.Upstream) (*Route53, error) { | func New(ctx context.Context, c route53iface.Route53API, keys map[string][]string, up *upstream.Upstream, refresh time.Duration) (*Route53, error) { | ||||||
| 	zones := make(map[string][]*zone, len(keys)) | 	zones := make(map[string][]*zone, len(keys)) | ||||||
| 	zoneNames := make([]string, 0, len(keys)) | 	zoneNames := make([]string, 0, len(keys)) | ||||||
| 	for dns, hostedZoneIDs := range keys { | 	for dns, hostedZoneIDs := range keys { | ||||||
| @@ -72,6 +73,7 @@ func New(ctx context.Context, c route53iface.Route53API, keys map[string][]strin | |||||||
| 		zoneNames: zoneNames, | 		zoneNames: zoneNames, | ||||||
| 		zones:     zones, | 		zones:     zones, | ||||||
| 		upstream:  up, | 		upstream:  up, | ||||||
|  | 		refresh:   refresh, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -87,7 +89,7 @@ func (h *Route53) Run(ctx context.Context) error { | |||||||
| 			case <-ctx.Done(): | 			case <-ctx.Done(): | ||||||
| 				log.Infof("Breaking out of Route53 update loop: %v", ctx.Err()) | 				log.Infof("Breaking out of Route53 update loop: %v", ctx.Err()) | ||||||
| 				return | 				return | ||||||
| 			case <-time.After(1 * time.Minute): | 			case <-time.After(h.refresh): | ||||||
| 				if err := h.updateZones(ctx); err != nil && ctx.Err() == nil /* Don't log error if ctx expired. */ { | 				if err := h.updateZones(ctx); err != nil && ctx.Err() == nil /* Don't log error if ctx expired. */ { | ||||||
| 					log.Errorf("Failed to update zones: %v", err) | 					log.Errorf("Failed to update zones: %v", err) | ||||||
| 				} | 				} | ||||||
| @@ -248,7 +250,7 @@ func (h *Route53) updateZones(ctx context.Context) error { | |||||||
| 				newZ.Upstream = h.upstream | 				newZ.Upstream = h.upstream | ||||||
| 				in := &route53.ListResourceRecordSetsInput{ | 				in := &route53.ListResourceRecordSetsInput{ | ||||||
| 					HostedZoneId: aws.String(hostedZone.id), | 					HostedZoneId: aws.String(hostedZone.id), | ||||||
| 					MaxItems: aws.String("1000"), | 					MaxItems:     aws.String("1000"), | ||||||
| 				} | 				} | ||||||
| 				err = h.client.ListResourceRecordSetsPagesWithContext(ctx, in, | 				err = h.client.ListResourceRecordSetsPagesWithContext(ctx, in, | ||||||
| 					func(out *route53.ListResourceRecordSetsOutput, last bool) bool { | 					func(out *route53.ListResourceRecordSetsOutput, last bool) bool { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/coredns/coredns/plugin/pkg/dnstest" | 	"github.com/coredns/coredns/plugin/pkg/dnstest" | ||||||
| 	"github.com/coredns/coredns/plugin/pkg/fall" | 	"github.com/coredns/coredns/plugin/pkg/fall" | ||||||
| @@ -79,7 +80,7 @@ func (fakeRoute53) ListResourceRecordSetsPagesWithContext(_ aws.Context, in *rou | |||||||
| func TestRoute53(t *testing.T) { | func TestRoute53(t *testing.T) { | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
|  |  | ||||||
| 	r, err := New(ctx, fakeRoute53{}, map[string][]string{"bad.": {"0987654321"}}, &upstream.Upstream{}) | 	r, err := New(ctx, fakeRoute53{}, map[string][]string{"bad.": {"0987654321"}}, &upstream.Upstream{}, time.Duration(1) * time.Minute) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Failed to create Route53: %v", err) | 		t.Fatalf("Failed to create Route53: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -87,7 +88,7 @@ func TestRoute53(t *testing.T) { | |||||||
| 		t.Fatalf("Expected errors for zone bad.") | 		t.Fatalf("Expected errors for zone bad.") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	r, err = New(ctx, fakeRoute53{}, map[string][]string{"org.": {"1357986420", "1234567890"}, "gov.": {"Z098765432", "1234567890"}}, &upstream.Upstream{}) | 	r, err = New(ctx, fakeRoute53{}, map[string][]string{"org.": {"1357986420", "1234567890"}, "gov.": {"Z098765432", "1234567890"}}, &upstream.Upstream{}, time.Duration(90) * time.Second) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Failed to create Route53: %v", err) | 		t.Fatalf("Failed to create Route53: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ package route53 | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/coredns/coredns/core/dnsserver" | 	"github.com/coredns/coredns/core/dnsserver" | ||||||
| 	"github.com/coredns/coredns/plugin" | 	"github.com/coredns/coredns/plugin" | ||||||
| @@ -53,6 +56,8 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro | |||||||
|  |  | ||||||
| 		up := upstream.New() | 		up := upstream.New() | ||||||
|  |  | ||||||
|  | 		refresh := time.Duration(1) * time.Minute // default update frequency to 1 minute | ||||||
|  |  | ||||||
| 		args := c.RemainingArgs() | 		args := c.RemainingArgs() | ||||||
|  |  | ||||||
| 		for i := 0; i < len(args); i++ { | 		for i := 0; i < len(args); i++ { | ||||||
| @@ -98,6 +103,23 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro | |||||||
| 				} | 				} | ||||||
| 			case "fallthrough": | 			case "fallthrough": | ||||||
| 				fall.SetZonesFromArgs(c.RemainingArgs()) | 				fall.SetZonesFromArgs(c.RemainingArgs()) | ||||||
|  | 			case "refresh": | ||||||
|  | 				if c.NextArg() { | ||||||
|  | 					refreshStr := c.Val() | ||||||
|  | 					_, err := strconv.Atoi(refreshStr) | ||||||
|  | 					if err == nil { | ||||||
|  | 						refreshStr = fmt.Sprintf("%ss", c.Val()) | ||||||
|  | 					} | ||||||
|  | 					refresh, err = time.ParseDuration(refreshStr) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return c.Errf("Unable to parse duration: '%v'", err) | ||||||
|  | 					} | ||||||
|  | 					if refresh <= 0 { | ||||||
|  | 						return c.Errf("refresh interval must be greater than 0: %s", refreshStr) | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					return c.ArgErr() | ||||||
|  | 				} | ||||||
| 			default: | 			default: | ||||||
| 				return c.Errf("unknown property '%s'", c.Val()) | 				return c.Errf("unknown property '%s'", c.Val()) | ||||||
| 			} | 			} | ||||||
| @@ -107,7 +129,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro | |||||||
| 		}) | 		}) | ||||||
| 		client := f(credentials.NewChainCredentials(providers)) | 		client := f(credentials.NewChainCredentials(providers)) | ||||||
| 		ctx := context.Background() | 		ctx := context.Background() | ||||||
| 		h, err := New(ctx, client, keys, up) | 		h, err := New(ctx, client, keys, up, refresh) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return c.Errf("failed to create Route53 plugin: %v", err) | 			return c.Errf("failed to create Route53 plugin: %v", err) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -51,6 +51,22 @@ func TestSetupRoute53(t *testing.T) { | |||||||
| 		{`route53 example.org:12345678 example.org:12345678 { | 		{`route53 example.org:12345678 example.org:12345678 { | ||||||
| 	}`, true}, | 	}`, true}, | ||||||
|  |  | ||||||
|  | 		{`route53 example.org:12345678 { | ||||||
|  | 	refresh 90 | ||||||
|  | }`, false}, | ||||||
|  | 		{`route53 example.org:12345678 { | ||||||
|  | 	refresh 5m | ||||||
|  | }`, false}, | ||||||
|  | 		{`route53 example.org:12345678 { | ||||||
|  | 	refresh | ||||||
|  | }`, true}, | ||||||
|  | 		{`route53 example.org:12345678 { | ||||||
|  | 	refresh foo | ||||||
|  | }`, true}, | ||||||
|  | 		{`route53 example.org:12345678 { | ||||||
|  | 	refresh -1m | ||||||
|  | }`, true}, | ||||||
|  |  | ||||||
| 		{`route53 example.org { | 		{`route53 example.org { | ||||||
| 	}`, true}, | 	}`, true}, | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user