mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-11-28 16:31:23 +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