mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-11-29 08:51:25 +01:00
feat(plan): first implementation of plan
This commit is contained in:
parent
786055f3b3
commit
bd8dd3d499
75
plan/plan.go
Normal file
75
plan/plan.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package plan
|
||||||
|
|
||||||
|
// DNSRecord holds information about a DNS record.
|
||||||
|
type DNSRecord struct {
|
||||||
|
// The hostname of the DNS record
|
||||||
|
DNSName string
|
||||||
|
// The target the DNS record points to
|
||||||
|
Target string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plan can convert a list of desired and current records to a series of create,
|
||||||
|
// update and delete actions.
|
||||||
|
type Plan struct {
|
||||||
|
// List of current records
|
||||||
|
Current []DNSRecord
|
||||||
|
// List of desired records
|
||||||
|
Desired []DNSRecord
|
||||||
|
|
||||||
|
// The following lists hold actions to take in orer to move current state to
|
||||||
|
// desired state.
|
||||||
|
|
||||||
|
// Records that need to be created
|
||||||
|
Create []DNSRecord
|
||||||
|
// Records that need to be updated (current data)
|
||||||
|
UpdateOld []DNSRecord
|
||||||
|
// Records that need to be updated (desired data)
|
||||||
|
UpdateNew []DNSRecord
|
||||||
|
// Records that need to be deleted
|
||||||
|
Delete []DNSRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate computes the actions needed to move current state to desired state.
|
||||||
|
func (p *Plan) Calculate() *Plan {
|
||||||
|
plan := &Plan{}
|
||||||
|
|
||||||
|
// Ensure all desired records exist. For each desired record make sure it's
|
||||||
|
// either created or updated.
|
||||||
|
for _, desired := range p.Desired {
|
||||||
|
// Get the matching current record if it exists.
|
||||||
|
current, exists := recordExists(desired, p.Current)
|
||||||
|
|
||||||
|
// If there's no current record create desired record.
|
||||||
|
if !exists {
|
||||||
|
plan.Create = append(plan.Create, desired)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there already is a record update it if it changed.
|
||||||
|
if desired.Target != current.Target {
|
||||||
|
plan.UpdateOld = append(plan.UpdateOld, current)
|
||||||
|
plan.UpdateNew = append(plan.UpdateNew, desired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all undesired records are removed. Each current record that cannot
|
||||||
|
// be found in the list of desired records is removed.
|
||||||
|
for _, current := range p.Current {
|
||||||
|
if _, exists := recordExists(current, p.Desired); !exists {
|
||||||
|
plan.Delete = append(plan.Delete, current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan
|
||||||
|
}
|
||||||
|
|
||||||
|
// recordExists checks whether a record can be found in a list of records.
|
||||||
|
func recordExists(needle DNSRecord, haystack []DNSRecord) (DNSRecord, bool) {
|
||||||
|
for _, record := range haystack {
|
||||||
|
if record.DNSName == needle.DNSName {
|
||||||
|
return record, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DNSRecord{}, false
|
||||||
|
}
|
||||||
112
plan/plan_test.go
Normal file
112
plan/plan_test.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package plan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestCalculate tests that a plan can calculate actions to move a list of
|
||||||
|
// current records to a list of desired records.
|
||||||
|
func TestCalculate(t *testing.T) {
|
||||||
|
// empty list of records
|
||||||
|
empty := []DNSRecord{}
|
||||||
|
// a simple entry
|
||||||
|
fooV1 := []DNSRecord{{DNSName: "foo", Target: "v1"}}
|
||||||
|
// the same entry but with different target
|
||||||
|
fooV2 := []DNSRecord{{DNSName: "foo", Target: "v2"}}
|
||||||
|
// another simple entry
|
||||||
|
bar := []DNSRecord{{DNSName: "bar", Target: "v1"}}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
current, desired, create, updateOld, updateNew, delete []DNSRecord
|
||||||
|
}{
|
||||||
|
// Nothing exists and nothing desired doesn't change anything.
|
||||||
|
{empty, empty, empty, empty, empty, empty},
|
||||||
|
// More desired than current creates the desired.
|
||||||
|
{empty, fooV1, fooV1, empty, empty, empty},
|
||||||
|
// Desired equals current doesn't change anything.
|
||||||
|
{fooV1, fooV1, empty, empty, empty, empty},
|
||||||
|
// Nothing is desired deletes the current.
|
||||||
|
{fooV1, empty, empty, empty, empty, fooV1},
|
||||||
|
// Current and desired match but Target is different triggers an update.
|
||||||
|
{fooV1, fooV2, empty, fooV1, fooV2, empty},
|
||||||
|
// Both exist but are different creates desired and deletes current.
|
||||||
|
{fooV1, bar, bar, empty, empty, fooV1},
|
||||||
|
} {
|
||||||
|
// setup plan
|
||||||
|
plan := &Plan{
|
||||||
|
Current: tc.current,
|
||||||
|
Desired: tc.desired,
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate actions
|
||||||
|
plan = plan.Calculate()
|
||||||
|
|
||||||
|
// validate actions
|
||||||
|
validateEntries(t, plan.Create, tc.create)
|
||||||
|
validateEntries(t, plan.UpdateOld, tc.updateOld)
|
||||||
|
validateEntries(t, plan.UpdateNew, tc.updateNew)
|
||||||
|
validateEntries(t, plan.Delete, tc.delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkCalculate benchmarks the Calculate method.
|
||||||
|
func BenchmarkCalculate(b *testing.B) {
|
||||||
|
foo := DNSRecord{DNSName: "foo", Target: "v1"}
|
||||||
|
barV1 := DNSRecord{DNSName: "bar", Target: "v1"}
|
||||||
|
barV2 := DNSRecord{DNSName: "bar", Target: "v2"}
|
||||||
|
baz := DNSRecord{DNSName: "baz", Target: "v1"}
|
||||||
|
|
||||||
|
plan := &Plan{
|
||||||
|
Current: []DNSRecord{foo, barV1},
|
||||||
|
Desired: []DNSRecord{barV2, baz},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
plan.Calculate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExamplePlan shows how plan can be used.
|
||||||
|
func ExamplePlan() {
|
||||||
|
foo := DNSRecord{DNSName: "foo.example.com", Target: "1.2.3.4"}
|
||||||
|
barV1 := DNSRecord{DNSName: "bar.example.com", Target: "8.8.8.8"}
|
||||||
|
barV2 := DNSRecord{DNSName: "bar.example.com", Target: "8.8.4.4"}
|
||||||
|
baz := DNSRecord{DNSName: "baz.example.com", Target: "6.6.6.6"}
|
||||||
|
|
||||||
|
// Plan where
|
||||||
|
// * foo should be deleted
|
||||||
|
// * bar should be updated from v1 to v2
|
||||||
|
// * baz should be created
|
||||||
|
plan := &Plan{
|
||||||
|
Current: []DNSRecord{foo, barV1},
|
||||||
|
Desired: []DNSRecord{barV2, baz},
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate actions
|
||||||
|
plan = plan.Calculate()
|
||||||
|
|
||||||
|
// print actions
|
||||||
|
fmt.Println("Create:", plan.Create)
|
||||||
|
fmt.Println("UpdateOld:", plan.UpdateOld)
|
||||||
|
fmt.Println("UpdateNew:", plan.UpdateNew)
|
||||||
|
fmt.Println("Delete:", plan.Delete)
|
||||||
|
// Output:
|
||||||
|
// Create: [{baz.example.com 6.6.6.6}]
|
||||||
|
// UpdateOld: [{bar.example.com 8.8.8.8}]
|
||||||
|
// UpdateNew: [{bar.example.com 8.8.4.4}]
|
||||||
|
// Delete: [{foo.example.com 1.2.3.4}]
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateEntries validates that the list of entries matches expected.
|
||||||
|
func validateEntries(t *testing.T, entries, expected []DNSRecord) {
|
||||||
|
if len(entries) != len(expected) {
|
||||||
|
t.Fatalf("expected %q to match %q", entries, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range entries {
|
||||||
|
if entries[i] != expected[i] {
|
||||||
|
t.Fatalf("expected %q to match %q", entries, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user