feat(plan): first implementation of plan

This commit is contained in:
Martin Linkhorst 2017-02-21 13:49:28 +01:00
parent 786055f3b3
commit bd8dd3d499
No known key found for this signature in database
GPG Key ID: 032A0944BB83C4B9
2 changed files with 187 additions and 0 deletions

75
plan/plan.go Normal file
View 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
View 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)
}
}
}