Separate plan by record type

This commit is contained in:
John Gardiner Myers 2021-11-06 19:34:34 -07:00
parent 06227c1fbf
commit 94d09b37dc
3 changed files with 98 additions and 74 deletions

View File

@ -30,6 +30,8 @@ import (
const ( const (
// RecordTypeA is a RecordType enum value // RecordTypeA is a RecordType enum value
RecordTypeA = "A" RecordTypeA = "A"
// RecordTypeAAAA is a RecordType enum value
RecordTypeAAAA = "AAAA"
// RecordTypeCNAME is a RecordType enum value // RecordTypeCNAME is a RecordType enum value
RecordTypeCNAME = "CNAME" RecordTypeCNAME = "CNAME"
// RecordTypeTXT is a RecordType enum value // RecordTypeTXT is a RecordType enum value

View File

@ -78,12 +78,12 @@ bar.com | | [->191.1.1.1, ->190.1.1.1] | = create (bar.com -> 1
"=", i.e. result of calculation relies on supplied ConflictResolver "=", i.e. result of calculation relies on supplied ConflictResolver
*/ */
type planTable struct { type planTable struct {
rows map[string]map[string]*planTableRow rows map[string]map[string]map[string]*planTableRow
resolver ConflictResolver resolver ConflictResolver
} }
func newPlanTable() planTable { // TODO: make resolver configurable func newPlanTable() planTable { // TODO: make resolver configurable
return planTable{map[string]map[string]*planTableRow{}, PerResource{}} return planTable{map[string]map[string]map[string]*planTableRow{}, PerResource{}}
} }
// planTableRow // planTableRow
@ -101,23 +101,29 @@ func (t planTableRow) String() string {
func (t planTable) addCurrent(e *endpoint.Endpoint) { func (t planTable) addCurrent(e *endpoint.Endpoint) {
dnsName := normalizeDNSName(e.DNSName) dnsName := normalizeDNSName(e.DNSName)
if _, ok := t.rows[dnsName]; !ok { if _, ok := t.rows[dnsName]; !ok {
t.rows[dnsName] = make(map[string]*planTableRow) t.rows[dnsName] = make(map[string]map[string]*planTableRow)
} }
if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok { if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok {
t.rows[dnsName][e.SetIdentifier] = &planTableRow{} t.rows[dnsName][e.SetIdentifier] = make(map[string]*planTableRow)
} }
t.rows[dnsName][e.SetIdentifier].current = e if _, ok := t.rows[e.SetIdentifier][e.RecordType]; !ok {
t.rows[dnsName][e.SetIdentifier][e.RecordType] = &planTableRow{}
}
t.rows[dnsName][e.SetIdentifier][e.RecordType].current = e
} }
func (t planTable) addCandidate(e *endpoint.Endpoint) { func (t planTable) addCandidate(e *endpoint.Endpoint) {
dnsName := normalizeDNSName(e.DNSName) dnsName := normalizeDNSName(e.DNSName)
if _, ok := t.rows[dnsName]; !ok { if _, ok := t.rows[dnsName]; !ok {
t.rows[dnsName] = make(map[string]*planTableRow) t.rows[dnsName] = make(map[string]map[string]*planTableRow)
} }
if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok { if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok {
t.rows[dnsName][e.SetIdentifier] = &planTableRow{} t.rows[dnsName][e.SetIdentifier] = make(map[string]*planTableRow)
} }
t.rows[dnsName][e.SetIdentifier].candidates = append(t.rows[dnsName][e.SetIdentifier].candidates, e) if _, ok := t.rows[dnsName][e.SetIdentifier][e.RecordType]; !ok {
t.rows[dnsName][e.SetIdentifier][e.RecordType] = &planTableRow{}
}
t.rows[dnsName][e.SetIdentifier][e.RecordType].candidates = append(t.rows[dnsName][e.SetIdentifier][e.RecordType].candidates, e)
} }
func (c *Changes) HasChanges() bool { func (c *Changes) HasChanges() bool {
@ -147,24 +153,26 @@ func (p *Plan) Calculate() *Plan {
changes := &Changes{} changes := &Changes{}
for _, topRow := range t.rows { for _, topRow := range t.rows {
for _, row := range topRow { for _, midRow := range topRow {
if row.current == nil { // dns name not taken for _, row := range midRow {
changes.Create = append(changes.Create, t.resolver.ResolveCreate(row.candidates)) if row.current == nil { // dns name not taken
} changes.Create = append(changes.Create, t.resolver.ResolveCreate(row.candidates))
if row.current != nil && len(row.candidates) == 0 { }
changes.Delete = append(changes.Delete, row.current) if row.current != nil && len(row.candidates) == 0 {
} changes.Delete = append(changes.Delete, row.current)
}
// TODO: allows record type change, which might not be supported by all dns providers
if row.current != nil && len(row.candidates) > 0 { // dns name is taken // TODO: allows record type change, which might not be supported by all dns providers
update := t.resolver.ResolveUpdate(row.current, row.candidates) if row.current != nil && len(row.candidates) > 0 { // dns name is taken
// compare "update" to "current" to figure out if actual update is required update := t.resolver.ResolveUpdate(row.current, row.candidates)
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) { // compare "update" to "current" to figure out if actual update is required
inheritOwner(row.current, update) if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) {
changes.UpdateNew = append(changes.UpdateNew, update) inheritOwner(row.current, update)
changes.UpdateOld = append(changes.UpdateOld, row.current) changes.UpdateNew = append(changes.UpdateNew, update)
changes.UpdateOld = append(changes.UpdateOld, row.current)
}
continue
} }
continue
} }
} }
} }
@ -181,7 +189,7 @@ func (p *Plan) Calculate() *Plan {
Current: p.Current, Current: p.Current,
Desired: p.Desired, Desired: p.Desired,
Changes: changes, Changes: changes,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
} }
return plan return plan

View File

@ -35,6 +35,9 @@ type PlanTestSuite struct {
fooV2CnameNoLabel *endpoint.Endpoint fooV2CnameNoLabel *endpoint.Endpoint
fooV3CnameSameResource *endpoint.Endpoint fooV3CnameSameResource *endpoint.Endpoint
fooA5 *endpoint.Endpoint fooA5 *endpoint.Endpoint
fooAAAA *endpoint.Endpoint
dsA *endpoint.Endpoint
dsAAAA *endpoint.Endpoint
bar127A *endpoint.Endpoint bar127A *endpoint.Endpoint
bar127AWithTTL *endpoint.Endpoint bar127AWithTTL *endpoint.Endpoint
bar127AWithProviderSpecificTrue *endpoint.Endpoint bar127AWithProviderSpecificTrue *endpoint.Endpoint
@ -106,6 +109,30 @@ func (suite *PlanTestSuite) SetupTest() {
endpoint.ResourceLabelKey: "ingress/default/foo-5", endpoint.ResourceLabelKey: "ingress/default/foo-5",
}, },
} }
suite.fooAAAA = &endpoint.Endpoint{
DNSName: "foo",
Targets: endpoint.Targets{"2001:DB8::1"},
RecordType: "AAAA",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-AAAA",
},
}
suite.dsA = &endpoint.Endpoint{
DNSName: "ds",
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/ds",
},
}
suite.dsAAAA = &endpoint.Endpoint{
DNSName: "ds",
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "AAAA",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/ds-AAAAA",
},
}
suite.bar127A = &endpoint.Endpoint{ suite.bar127A = &endpoint.Endpoint{
DNSName: "bar", DNSName: "bar",
Targets: endpoint.Targets{"127.0.0.1"}, Targets: endpoint.Targets{"127.0.0.1"},
@ -438,9 +465,9 @@ func (suite *PlanTestSuite) TestIdempotency() {
func (suite *PlanTestSuite) TestDifferentTypes() { func (suite *PlanTestSuite) TestDifferentTypes() {
current := []*endpoint.Endpoint{suite.fooV1Cname} current := []*endpoint.Endpoint{suite.fooV1Cname}
desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooA5} desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooA5}
expectedCreate := []*endpoint.Endpoint{} expectedCreate := []*endpoint.Endpoint{suite.fooA5}
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname} expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooA5} expectedUpdateNew := []*endpoint.Endpoint{suite.fooV2Cname}
expectedDelete := []*endpoint.Endpoint{} expectedDelete := []*endpoint.Endpoint{}
p := &Plan{ p := &Plan{
@ -544,52 +571,6 @@ func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() {
validateEntries(suite.T(), changes.Delete, expectedDelete) validateEntries(suite.T(), changes.Delete, expectedDelete)
} }
// TODO: remove once multiple-target per endpoint is supported
func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceReplace() {
current := []*endpoint.Endpoint{suite.fooV3CnameSameResource, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV3CnameSameResource}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooV1Cname}
expectedDelete := []*endpoint.Endpoint{suite.bar192A}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}
changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
validateEntries(suite.T(), changes.Delete, expectedDelete)
}
// TODO: remove once multiple-target per endpoint is supported
func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceRetain() {
current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{}
expectedUpdateNew := []*endpoint.Endpoint{}
expectedDelete := []*endpoint.Endpoint{suite.bar192A}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}
changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
validateEntries(suite.T(), changes.Delete, expectedDelete)
}
func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() { func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() {
current := []*endpoint.Endpoint{suite.multiple1} current := []*endpoint.Endpoint{suite.multiple1}
desired := []*endpoint.Endpoint{suite.multiple2, suite.multiple3} desired := []*endpoint.Endpoint{suite.multiple2, suite.multiple3}
@ -695,6 +676,39 @@ func (suite *PlanTestSuite) TestMissing() {
validateEntries(suite.T(), changes.Create, expectedCreate) validateEntries(suite.T(), changes.Create, expectedCreate)
} }
func (suite *PlanTestSuite) TestAAAARecords() {
current := []*endpoint.Endpoint{}
desired := []*endpoint.Endpoint{suite.fooAAAA}
expectedCreate := []*endpoint.Endpoint{suite.fooAAAA}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}
changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
}
func (suite *PlanTestSuite) TestDualStackRecords() {
current := []*endpoint.Endpoint{}
desired := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
expectedCreate := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}
changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
}
func TestPlan(t *testing.T) { func TestPlan(t *testing.T) {
suite.Run(t, new(PlanTestSuite)) suite.Run(t, new(PlanTestSuite))
} }