Validate AWS record values size during batch set generation

This commit is contained in:
Megum1n 2023-12-19 10:21:47 +01:00
parent b9da66a524
commit c391f5588a
No known key found for this signature in database
GPG Key ID: 6A5CB0EBED11E256
5 changed files with 338 additions and 58 deletions

22
main.go
View File

@ -227,16 +227,18 @@ func main() {
case "aws": case "aws":
p, err = aws.NewAWSProvider( p, err = aws.NewAWSProvider(
aws.AWSConfig{ aws.AWSConfig{
DomainFilter: domainFilter, DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter, ZoneIDFilter: zoneIDFilter,
ZoneTypeFilter: zoneTypeFilter, ZoneTypeFilter: zoneTypeFilter,
ZoneTagFilter: zoneTagFilter, ZoneTagFilter: zoneTagFilter,
BatchChangeSize: cfg.AWSBatchChangeSize, BatchChangeSize: cfg.AWSBatchChangeSize,
BatchChangeInterval: cfg.AWSBatchChangeInterval, BatchChangeSizeBytes: cfg.AWSBatchChangeSizeBytes,
EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth, BatchChangeSizeValues: cfg.AWSBatchChangeSizeValues,
PreferCNAME: cfg.AWSPreferCNAME, BatchChangeInterval: cfg.AWSBatchChangeInterval,
DryRun: cfg.DryRun, EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
ZoneCacheDuration: cfg.AWSZoneCacheDuration, PreferCNAME: cfg.AWSPreferCNAME,
DryRun: cfg.DryRun,
ZoneCacheDuration: cfg.AWSZoneCacheDuration,
}, },
route53.New(awsSession), route53.New(awsSession),
) )

View File

@ -86,6 +86,8 @@ type Config struct {
AWSAssumeRole string AWSAssumeRole string
AWSAssumeRoleExternalID string AWSAssumeRoleExternalID string
AWSBatchChangeSize int AWSBatchChangeSize int
AWSBatchChangeSizeBytes int
AWSBatchChangeSizeValues int
AWSBatchChangeInterval time.Duration AWSBatchChangeInterval time.Duration
AWSEvaluateTargetHealth bool AWSEvaluateTargetHealth bool
AWSAPIRetries int AWSAPIRetries int
@ -258,6 +260,8 @@ var defaultConfig = &Config{
AWSAssumeRole: "", AWSAssumeRole: "",
AWSAssumeRoleExternalID: "", AWSAssumeRoleExternalID: "",
AWSBatchChangeSize: 1000, AWSBatchChangeSize: 1000,
AWSBatchChangeSizeBytes: 32000,
AWSBatchChangeSizeValues: 1000,
AWSBatchChangeInterval: time.Second, AWSBatchChangeInterval: time.Second,
AWSEvaluateTargetHealth: true, AWSEvaluateTargetHealth: true,
AWSAPIRetries: 3, AWSAPIRetries: 3,
@ -477,6 +481,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("aws-assume-role", "When using the AWS API, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional)").Default(defaultConfig.AWSAssumeRole).StringVar(&cfg.AWSAssumeRole) app.Flag("aws-assume-role", "When using the AWS API, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional)").Default(defaultConfig.AWSAssumeRole).StringVar(&cfg.AWSAssumeRole)
app.Flag("aws-assume-role-external-id", "When using the AWS API and assuming a role then specify this external ID` (optional)").Default(defaultConfig.AWSAssumeRoleExternalID).StringVar(&cfg.AWSAssumeRoleExternalID) app.Flag("aws-assume-role-external-id", "When using the AWS API and assuming a role then specify this external ID` (optional)").Default(defaultConfig.AWSAssumeRoleExternalID).StringVar(&cfg.AWSAssumeRoleExternalID)
app.Flag("aws-batch-change-size", "When using the AWS provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.AWSBatchChangeSize)).IntVar(&cfg.AWSBatchChangeSize) app.Flag("aws-batch-change-size", "When using the AWS provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.AWSBatchChangeSize)).IntVar(&cfg.AWSBatchChangeSize)
app.Flag("aws-batch-change-size-bytes", "When using the AWS provider, set the maximum byte size that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.AWSBatchChangeSizeBytes)).IntVar(&cfg.AWSBatchChangeSizeBytes)
app.Flag("aws-batch-change-size-values", "When using the AWS provider, set the maximum total record values that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.AWSBatchChangeSizeValues)).IntVar(&cfg.AWSBatchChangeSizeValues)
app.Flag("aws-batch-change-interval", "When using the AWS provider, set the interval between batch changes.").Default(defaultConfig.AWSBatchChangeInterval.String()).DurationVar(&cfg.AWSBatchChangeInterval) app.Flag("aws-batch-change-interval", "When using the AWS provider, set the interval between batch changes.").Default(defaultConfig.AWSBatchChangeInterval.String()).DurationVar(&cfg.AWSBatchChangeInterval)
app.Flag("aws-evaluate-target-health", "When using the AWS provider, set whether to evaluate the health of a DNS target (default: enabled, disable with --no-aws-evaluate-target-health)").Default(strconv.FormatBool(defaultConfig.AWSEvaluateTargetHealth)).BoolVar(&cfg.AWSEvaluateTargetHealth) app.Flag("aws-evaluate-target-health", "When using the AWS provider, set whether to evaluate the health of a DNS target (default: enabled, disable with --no-aws-evaluate-target-health)").Default(strconv.FormatBool(defaultConfig.AWSEvaluateTargetHealth)).BoolVar(&cfg.AWSEvaluateTargetHealth)
app.Flag("aws-api-retries", "When using the AWS API, set the maximum number of retries before giving up.").Default(strconv.Itoa(defaultConfig.AWSAPIRetries)).IntVar(&cfg.AWSAPIRetries) app.Flag("aws-api-retries", "When using the AWS API, set the maximum number of retries before giving up.").Default(strconv.Itoa(defaultConfig.AWSAPIRetries)).IntVar(&cfg.AWSAPIRetries)

View File

@ -58,6 +58,8 @@ var (
AWSAssumeRole: "", AWSAssumeRole: "",
AWSAssumeRoleExternalID: "", AWSAssumeRoleExternalID: "",
AWSBatchChangeSize: 1000, AWSBatchChangeSize: 1000,
AWSBatchChangeSizeBytes: 32000,
AWSBatchChangeSizeValues: 1000,
AWSBatchChangeInterval: time.Second, AWSBatchChangeInterval: time.Second,
AWSEvaluateTargetHealth: true, AWSEvaluateTargetHealth: true,
AWSAPIRetries: 3, AWSAPIRetries: 3,
@ -169,6 +171,8 @@ var (
AWSAssumeRole: "some-other-role", AWSAssumeRole: "some-other-role",
AWSAssumeRoleExternalID: "pg2000", AWSAssumeRoleExternalID: "pg2000",
AWSBatchChangeSize: 100, AWSBatchChangeSize: 100,
AWSBatchChangeSizeBytes: 16000,
AWSBatchChangeSizeValues: 100,
AWSBatchChangeInterval: time.Second * 2, AWSBatchChangeInterval: time.Second * 2,
AWSEvaluateTargetHealth: false, AWSEvaluateTargetHealth: false,
AWSAPIRetries: 13, AWSAPIRetries: 13,
@ -354,6 +358,8 @@ func TestParseFlags(t *testing.T) {
"--aws-assume-role=some-other-role", "--aws-assume-role=some-other-role",
"--aws-assume-role-external-id=pg2000", "--aws-assume-role-external-id=pg2000",
"--aws-batch-change-size=100", "--aws-batch-change-size=100",
"--aws-batch-change-size-bytes=16000",
"--aws-batch-change-size-values=100",
"--aws-batch-change-interval=2s", "--aws-batch-change-interval=2s",
"--aws-api-retries=13", "--aws-api-retries=13",
"--aws-prefer-cname", "--aws-prefer-cname",
@ -476,6 +482,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_AWS_ASSUME_ROLE": "some-other-role", "EXTERNAL_DNS_AWS_ASSUME_ROLE": "some-other-role",
"EXTERNAL_DNS_AWS_ASSUME_ROLE_EXTERNAL_ID": "pg2000", "EXTERNAL_DNS_AWS_ASSUME_ROLE_EXTERNAL_ID": "pg2000",
"EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE": "100", "EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE": "100",
"EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE_BYTES": "16000",
"EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE_VALUES": "100",
"EXTERNAL_DNS_AWS_BATCH_CHANGE_INTERVAL": "2s", "EXTERNAL_DNS_AWS_BATCH_CHANGE_INTERVAL": "2s",
"EXTERNAL_DNS_AWS_EVALUATE_TARGET_HEALTH": "0", "EXTERNAL_DNS_AWS_EVALUATE_TARGET_HEALTH": "0",
"EXTERNAL_DNS_AWS_API_RETRIES": "13", "EXTERNAL_DNS_AWS_API_RETRIES": "13",

View File

@ -206,6 +206,8 @@ type Route53API interface {
type Route53Change struct { type Route53Change struct {
route53.Change route53.Change
OwnedRecord string OwnedRecord string
sizeBytes int
sizeValues int
} }
type Route53Changes []*Route53Change type Route53Changes []*Route53Change
@ -227,11 +229,13 @@ type zonesListCache struct {
// AWSProvider is an implementation of Provider for AWS Route53. // AWSProvider is an implementation of Provider for AWS Route53.
type AWSProvider struct { type AWSProvider struct {
provider.BaseProvider provider.BaseProvider
client Route53API client Route53API
dryRun bool dryRun bool
batchChangeSize int batchChangeSize int
batchChangeInterval time.Duration batchChangeSizeBytes int
evaluateTargetHealth bool batchChangeSizeValues int
batchChangeInterval time.Duration
evaluateTargetHealth bool
// only consider hosted zones managing domains ending in this suffix // only consider hosted zones managing domains ending in this suffix
domainFilter endpoint.DomainFilter domainFilter endpoint.DomainFilter
// filter hosted zones by id // filter hosted zones by id
@ -248,33 +252,37 @@ type AWSProvider struct {
// AWSConfig contains configuration to create a new AWS provider. // AWSConfig contains configuration to create a new AWS provider.
type AWSConfig struct { type AWSConfig struct {
DomainFilter endpoint.DomainFilter DomainFilter endpoint.DomainFilter
ZoneIDFilter provider.ZoneIDFilter ZoneIDFilter provider.ZoneIDFilter
ZoneTypeFilter provider.ZoneTypeFilter ZoneTypeFilter provider.ZoneTypeFilter
ZoneTagFilter provider.ZoneTagFilter ZoneTagFilter provider.ZoneTagFilter
BatchChangeSize int BatchChangeSize int
BatchChangeInterval time.Duration BatchChangeSizeBytes int
EvaluateTargetHealth bool BatchChangeSizeValues int
PreferCNAME bool BatchChangeInterval time.Duration
DryRun bool EvaluateTargetHealth bool
ZoneCacheDuration time.Duration PreferCNAME bool
DryRun bool
ZoneCacheDuration time.Duration
} }
// NewAWSProvider initializes a new AWS Route53 based Provider. // NewAWSProvider initializes a new AWS Route53 based Provider.
func NewAWSProvider(awsConfig AWSConfig, client Route53API) (*AWSProvider, error) { func NewAWSProvider(awsConfig AWSConfig, client Route53API) (*AWSProvider, error) {
provider := &AWSProvider{ provider := &AWSProvider{
client: client, client: client,
domainFilter: awsConfig.DomainFilter, domainFilter: awsConfig.DomainFilter,
zoneIDFilter: awsConfig.ZoneIDFilter, zoneIDFilter: awsConfig.ZoneIDFilter,
zoneTypeFilter: awsConfig.ZoneTypeFilter, zoneTypeFilter: awsConfig.ZoneTypeFilter,
zoneTagFilter: awsConfig.ZoneTagFilter, zoneTagFilter: awsConfig.ZoneTagFilter,
batchChangeSize: awsConfig.BatchChangeSize, batchChangeSize: awsConfig.BatchChangeSize,
batchChangeInterval: awsConfig.BatchChangeInterval, batchChangeSizeBytes: awsConfig.BatchChangeSizeBytes,
evaluateTargetHealth: awsConfig.EvaluateTargetHealth, batchChangeSizeValues: awsConfig.BatchChangeSizeValues,
preferCNAME: awsConfig.PreferCNAME, batchChangeInterval: awsConfig.BatchChangeInterval,
dryRun: awsConfig.DryRun, evaluateTargetHealth: awsConfig.EvaluateTargetHealth,
zonesCache: &zonesListCache{duration: awsConfig.ZoneCacheDuration}, preferCNAME: awsConfig.PreferCNAME,
failedChangesQueue: make(map[string]Route53Changes), dryRun: awsConfig.DryRun,
zonesCache: &zonesListCache{duration: awsConfig.ZoneCacheDuration},
failedChangesQueue: make(map[string]Route53Changes),
} }
return provider, nil return provider, nil
@ -565,7 +573,8 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes,
retriedChanges, newChanges := findChangesInQueue(cs, p.failedChangesQueue[z]) retriedChanges, newChanges := findChangesInQueue(cs, p.failedChangesQueue[z])
p.failedChangesQueue[z] = nil p.failedChangesQueue[z] = nil
batchCs := append(batchChangeSet(newChanges, p.batchChangeSize), batchChangeSet(retriedChanges, p.batchChangeSize)...) batchCs := append(batchChangeSet(newChanges, p.batchChangeSize, p.batchChangeSizeBytes, p.batchChangeSizeValues),
batchChangeSet(retriedChanges, p.batchChangeSize, p.batchChangeSizeBytes, p.batchChangeSizeValues)...)
for i, b := range batchCs { for i, b := range batchCs {
if len(b) == 0 { if len(b) == 0 {
continue continue
@ -719,6 +728,8 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
Name: aws.String(ep.DNSName), Name: aws.String(ep.DNSName),
}, },
}, },
sizeBytes: 0,
sizeValues: 0,
} }
dualstack := false dualstack := false
if targetHostedZone := isAWSAlias(ep); targetHostedZone != "" { if targetHostedZone := isAWSAlias(ep); targetHostedZone != "" {
@ -736,6 +747,8 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
HostedZoneId: aws.String(cleanZoneID(targetHostedZone)), HostedZoneId: aws.String(cleanZoneID(targetHostedZone)),
EvaluateTargetHealth: aws.Bool(evalTargetHealth), EvaluateTargetHealth: aws.Bool(evalTargetHealth),
} }
change.sizeBytes += len([]byte(ep.Targets[0]))
change.sizeValues += 1
} else { } else {
change.ResourceRecordSet.Type = aws.String(ep.RecordType) change.ResourceRecordSet.Type = aws.String(ep.RecordType)
if !ep.RecordTTL.IsConfigured() { if !ep.RecordTTL.IsConfigured() {
@ -748,9 +761,18 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
change.ResourceRecordSet.ResourceRecords[idx] = &route53.ResourceRecord{ change.ResourceRecordSet.ResourceRecords[idx] = &route53.ResourceRecord{
Value: aws.String(val), Value: aws.String(val),
} }
change.sizeBytes += len([]byte(val))
change.sizeValues += 1
} }
} }
if action == route53.ChangeActionUpsert {
// When the value of the Action element is UPSERT, each ResourceRecord element and each character in a Value
// element is counted twice
change.sizeBytes *= 2
change.sizeValues *= 2
}
setIdentifier := ep.SetIdentifier setIdentifier := ep.SetIdentifier
if setIdentifier != "" { if setIdentifier != "" {
change.ResourceRecordSet.SetIdentifier = aws.String(setIdentifier) change.ResourceRecordSet.SetIdentifier = aws.String(setIdentifier)
@ -856,8 +878,26 @@ func (p *AWSProvider) tagsForZone(ctx context.Context, zoneID string) (map[strin
return tagMap, nil return tagMap, nil
} }
func batchChangeSet(cs Route53Changes, batchSize int) []Route53Changes { // count bytes for all changes values
if len(cs) <= batchSize { func countChangeBytes(cs Route53Changes) int {
count := 0
for _, c := range cs {
count += c.sizeBytes
}
return count
}
// count total value count for all changes
func countChangeValues(cs Route53Changes) int {
count := 0
for _, c := range cs {
count += c.sizeValues
}
return count
}
func batchChangeSet(cs Route53Changes, batchSize int, batchSizeBytes int, batchSizeValues int) []Route53Changes {
if len(cs) <= batchSize && countChangeBytes(cs) <= batchSizeBytes && countChangeValues(cs) <= batchSizeValues {
res := sortChangesByActionNameType(cs) res := sortChangesByActionNameType(cs)
return []Route53Changes{res} return []Route53Changes{res}
} }
@ -880,7 +920,10 @@ func batchChangeSet(cs Route53Changes, batchSize int) []Route53Changes {
continue continue
} }
if len(currentBatch)+len(v) > batchSize { bytes := countChangeBytes(currentBatch) + countChangeBytes(v)
values := countChangeValues(currentBatch) + countChangeValues(v)
if len(currentBatch)+len(v) > batchSize || bytes > batchSizeBytes || values > batchSizeValues {
// currentBatch would be too large if we add this changeset; // currentBatch would be too large if we add this changeset;
// add currentBatch to batchChanges and start a new currentBatch // add currentBatch to batchChanges and start a new currentBatch
batchChanges = append(batchChanges, sortChangesByActionNameType(currentBatch)) batchChanges = append(batchChanges, sortChangesByActionNameType(currentBatch))

View File

@ -19,6 +19,7 @@ package aws
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"net" "net"
"sort" "sort"
"strings" "strings"
@ -39,9 +40,11 @@ import (
) )
const ( const (
defaultBatchChangeSize = 4000 defaultBatchChangeSize = 4000
defaultBatchChangeInterval = time.Second defaultBatchChangeSizeBytes = 32000
defaultEvaluateTargetHealth = true defaultBatchChangeSizeValues = 1000
defaultBatchChangeInterval = time.Second
defaultEvaluateTargetHealth = true
) )
// Compile time check for interface conformance // Compile time check for interface conformance
@ -1527,7 +1530,7 @@ func TestAWSBatchChangeSet(t *testing.T) {
}) })
} }
batchCs := batchChangeSet(cs, defaultBatchChangeSize) batchCs := batchChangeSet(cs, defaultBatchChangeSize, defaultBatchChangeSizeBytes, defaultBatchChangeSizeValues)
require.Equal(t, 1, len(batchCs)) require.Equal(t, 1, len(batchCs))
@ -1565,7 +1568,7 @@ func TestAWSBatchChangeSetExceeding(t *testing.T) {
) )
} }
batchCs := batchChangeSet(cs, testLimit) batchCs := batchChangeSet(cs, testLimit, defaultBatchChangeSizeBytes, defaultBatchChangeSizeValues)
require.Equal(t, expectedBatchCount, len(batchCs)) require.Equal(t, expectedBatchCount, len(batchCs))
@ -1603,11 +1606,227 @@ func TestAWSBatchChangeSetExceedingNameChange(t *testing.T) {
) )
} }
batchCs := batchChangeSet(cs, testLimit) batchCs := batchChangeSet(cs, testLimit, defaultBatchChangeSizeBytes, defaultBatchChangeSizeValues)
require.Equal(t, 0, len(batchCs)) require.Equal(t, 0, len(batchCs))
} }
func TestAWSBatchChangeSetExceedingBytesLimit(t *testing.T) {
var cs Route53Changes
const testCount = 50
const testLimit = 100
const groupSize = 2
// Bytes for each name
var testBytes = len([]byte("1.2.3.4")) + len([]byte("test-record"))
// testCount / groupSize / (testLimit // bytes)
var expectedBatchCountFloat = float64(testCount) / float64(groupSize) / float64(testLimit/testBytes)
// Round up
var expectedBatchCount = int(math.Floor(expectedBatchCountFloat + 0.5))
for i := 1; i <= testCount; i += groupSize {
cs = append(cs,
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionCreate),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("A"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("1.2.3.4"),
},
},
},
},
sizeBytes: len([]byte("1.2.3.4")),
sizeValues: 1,
},
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionCreate),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("TXT"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("txt-record"),
},
},
},
},
sizeBytes: len([]byte("txt-record")),
sizeValues: 1,
},
)
}
batchCs := batchChangeSet(cs, defaultBatchChangeSize, testLimit, defaultBatchChangeSizeValues)
require.Equal(t, expectedBatchCount, len(batchCs))
}
func TestAWSBatchChangeSetExceedingBytesLimitUpsert(t *testing.T) {
var cs Route53Changes
const testCount = 50
const testLimit = 100
const groupSize = 2
// Bytes for each name multiplied by 2 for Upsert records
var testBytes = (len([]byte("1.2.3.4")) + len([]byte("test-record"))) * 2
// testCount / groupSize / (testLimit // bytes)
var expectedBatchCountFloat = float64(testCount) / float64(groupSize) / float64(testLimit/testBytes)
// Round up
var expectedBatchCount = int(math.Floor(expectedBatchCountFloat + 0.5))
for i := 1; i <= testCount; i += groupSize {
cs = append(cs,
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionUpsert),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("A"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("1.2.3.4"),
},
},
},
},
sizeBytes: len([]byte("1.2.3.4")) * 2,
sizeValues: 1,
},
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionUpsert),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("TXT"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("txt-record"),
},
},
},
},
sizeBytes: len([]byte("txt-record")) * 2,
sizeValues: 1,
},
)
}
batchCs := batchChangeSet(cs, defaultBatchChangeSize, testLimit, defaultBatchChangeSizeValues)
require.Equal(t, expectedBatchCount, len(batchCs))
}
func TestAWSBatchChangeSetExceedingValuesLimit(t *testing.T) {
var cs Route53Changes
const testCount = 50
const testLimit = 100
const groupSize = 2
// Values for each group
const testValues = 2
// testCount / groupSize / (testLimit // bytes)
var expectedBatchCountFloat = float64(testCount) / float64(groupSize) / float64(testLimit/testValues)
// Round up
var expectedBatchCount = int(math.Floor(expectedBatchCountFloat + 0.5))
for i := 1; i <= testCount; i += groupSize {
cs = append(cs,
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionCreate),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("A"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("1.2.3.4"),
},
},
},
},
sizeBytes: len([]byte("1.2.3.4")),
sizeValues: 1,
},
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionCreate),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("TXT"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("txt-record"),
},
},
},
},
sizeBytes: len([]byte("txt-record")),
sizeValues: 1,
},
)
}
batchCs := batchChangeSet(cs, defaultBatchChangeSize, defaultBatchChangeSizeBytes, testLimit)
require.Equal(t, expectedBatchCount, len(batchCs))
}
func TestAWSBatchChangeSetExceedingValuesLimitUpsert(t *testing.T) {
var cs Route53Changes
const testCount = 50
const testLimit = 100
const groupSize = 2
// Values for each group multiplied by 2 for Upsert records
const testValues = 2 * 2
// testCount / groupSize / (testLimit // bytes)
var expectedBatchCountFloat = float64(testCount) / float64(groupSize) / float64(testLimit/testValues)
// Round up
var expectedBatchCount = int(math.Floor(expectedBatchCountFloat + 0.5))
for i := 1; i <= testCount; i += groupSize {
cs = append(cs,
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionUpsert),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("A"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("1.2.3.4"),
},
},
},
},
sizeBytes: len([]byte("1.2.3.4")) * 2,
sizeValues: 1,
},
&Route53Change{
Change: route53.Change{
Action: aws.String(route53.ChangeActionUpsert),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(fmt.Sprintf("host-%d", i)),
Type: aws.String("TXT"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String("txt-record"),
},
},
},
},
sizeBytes: len([]byte("txt-record")) * 2,
sizeValues: 1,
},
)
}
batchCs := batchChangeSet(cs, defaultBatchChangeSize, defaultBatchChangeSizeBytes, testLimit)
require.Equal(t, expectedBatchCount, len(batchCs))
}
func validateEndpoints(t *testing.T, provider *AWSProvider, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) { func validateEndpoints(t *testing.T, provider *AWSProvider, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %+v:%+v", endpoints, expected) assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %+v:%+v", endpoints, expected)
@ -1933,17 +2152,19 @@ func newAWSProviderWithTagFilter(t *testing.T, domainFilter endpoint.DomainFilte
client := NewRoute53APIStub(t) client := NewRoute53APIStub(t)
provider := &AWSProvider{ provider := &AWSProvider{
client: client, client: client,
batchChangeSize: defaultBatchChangeSize, batchChangeSize: defaultBatchChangeSize,
batchChangeInterval: defaultBatchChangeInterval, batchChangeSizeBytes: defaultBatchChangeSizeBytes,
evaluateTargetHealth: evaluateTargetHealth, batchChangeSizeValues: defaultBatchChangeSizeValues,
domainFilter: domainFilter, batchChangeInterval: defaultBatchChangeInterval,
zoneIDFilter: zoneIDFilter, evaluateTargetHealth: evaluateTargetHealth,
zoneTypeFilter: zoneTypeFilter, domainFilter: domainFilter,
zoneTagFilter: zoneTagFilter, zoneIDFilter: zoneIDFilter,
dryRun: false, zoneTypeFilter: zoneTypeFilter,
zonesCache: &zonesListCache{duration: 1 * time.Minute}, zoneTagFilter: zoneTagFilter,
failedChangesQueue: make(map[string]Route53Changes), dryRun: false,
zonesCache: &zonesListCache{duration: 1 * time.Minute},
failedChangesQueue: make(map[string]Route53Changes),
} }
createAWSZone(t, provider, &route53.HostedZone{ createAWSZone(t, provider, &route53.HostedZone{