Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 243e776b59 | |||
| 59612f157a |
@@ -28,9 +28,28 @@ type RenderInput struct {
|
|||||||
// cluster. Both variants are shipped in the ConfigMap; the entrypoint selects
|
// cluster. Both variants are shipped in the ConfigMap; the entrypoint selects
|
||||||
// one based on the pod ordinal.
|
// one based on the pod ordinal.
|
||||||
func RenderNamedConf(in RenderInput) (primary string, secondary string) {
|
func RenderNamedConf(in RenderInput) (primary string, secondary string) {
|
||||||
|
// client.List returns cache-ordered (non-deterministic) results, so sort
|
||||||
|
// every input slice before rendering. Otherwise the rendered config
|
||||||
|
// reshuffles between reconciles, churning the ConfigMap — and with the
|
||||||
|
// pod-template config hash that means an endless rolling restart.
|
||||||
|
sortInput(&in)
|
||||||
return render(in, true), render(in, false)
|
return render(in, true), render(in, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortInput orders every list rendered into named.conf deterministically.
|
||||||
|
func sortInput(in *RenderInput) {
|
||||||
|
sort.Slice(in.ACLs, func(i, j int) bool { return in.ACLs[i].Name < in.ACLs[j].Name })
|
||||||
|
sort.Slice(in.Views, func(i, j int) bool {
|
||||||
|
if in.Views[i].Spec.Order != in.Views[j].Spec.Order {
|
||||||
|
return in.Views[i].Spec.Order < in.Views[j].Spec.Order
|
||||||
|
}
|
||||||
|
return in.Views[i].Name < in.Views[j].Name
|
||||||
|
})
|
||||||
|
sort.Slice(in.Forwards, func(i, j int) bool { return in.Forwards[i].Spec.ZoneName < in.Forwards[j].Spec.ZoneName })
|
||||||
|
sort.Slice(in.Policies, func(i, j int) bool { return in.Policies[i].Spec.ZoneName < in.Policies[j].Spec.ZoneName })
|
||||||
|
sort.Slice(in.DNSSECPolicies, func(i, j int) bool { return in.DNSSECPolicies[i].Name < in.DNSSECPolicies[j].Name })
|
||||||
|
}
|
||||||
|
|
||||||
func render(in RenderInput, isPrimary bool) string {
|
func render(in RenderInput, isPrimary bool) string {
|
||||||
c := in.Cluster
|
c := in.Cluster
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
|
|||||||
@@ -146,3 +146,34 @@ func TestCatalogHashStable(t *testing.T) {
|
|||||||
t.Fatalf("expected 40-char hex sha1, got %d: %s", len(h1), h1)
|
t.Fatalf("expected 40-char hex sha1, got %d: %s", len(h1), h1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderDeterministicWithShuffledForwards(t *testing.T) {
|
||||||
|
// client.List order is non-deterministic; the render must not depend on
|
||||||
|
// input order, or the ConfigMap churns and (with the config hash) the
|
||||||
|
// StatefulSet rolls forever.
|
||||||
|
mkFwd := func(zone, fwd string) bindv1alpha1.BindZone {
|
||||||
|
return bindv1alpha1.BindZone{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: zone},
|
||||||
|
Spec: bindv1alpha1.BindZoneSpec{
|
||||||
|
ClusterRef: "r", Type: bindv1alpha1.ZoneForward,
|
||||||
|
ZoneName: zone, Forwarders: []string{fwd},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
base := RenderInput{Cluster: newCluster(bindv1alpha1.ModeResolver)}
|
||||||
|
orderA := base
|
||||||
|
orderA.Forwards = []bindv1alpha1.BindZone{
|
||||||
|
mkFwd("unkin.net", "198.18.200.6"), mkFwd("consul", "198.18.19.14"),
|
||||||
|
mkFwd("k8s.syd1.au.unkin.net", "198.18.200.8"), mkFwd("13.18.198.in-addr.arpa", "198.18.200.6"),
|
||||||
|
}
|
||||||
|
orderB := base
|
||||||
|
orderB.Forwards = []bindv1alpha1.BindZone{
|
||||||
|
mkFwd("13.18.198.in-addr.arpa", "198.18.200.6"), mkFwd("k8s.syd1.au.unkin.net", "198.18.200.8"),
|
||||||
|
mkFwd("consul", "198.18.19.14"), mkFwd("unkin.net", "198.18.200.6"),
|
||||||
|
}
|
||||||
|
pa, _ := RenderNamedConf(orderA)
|
||||||
|
pb, _ := RenderNamedConf(orderB)
|
||||||
|
if pa != pb {
|
||||||
|
t.Fatalf("render must be independent of forward-zone input order:\n--- A ---\n%s\n--- B ---\n%s", pa, pb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user