fix: flip flop on ordering when reading and writing the annotation

Signed-off-by: Marcel Fest <marcel.fest@telekom.de>
This commit is contained in:
Marcel Fest
2026-09-15 13:49:52 +02:00
committed by GitHub
parent b85b0ee4d5
commit 7013019f46
2 changed files with 53 additions and 11 deletions

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/netip"
"slices"
"strings"
)
@@ -56,14 +57,14 @@ func ParseLeaseVIPs(value string) (LeaseVIPsValue, error) {
if err != nil {
return LeaseVIPsValue{}, fmt.Errorf("invalid %s VIP at index %d: %w", LeaseVIPs, vip.Index, err)
}
parsed.VIPs[index].Value = address
parsed.VIPs[index].Value = address.String()
}
return parsed, nil
}
func normalizeLeaseVIPs(values []string) []LeaseVIP {
unique := make(map[string]struct{})
result := make([]LeaseVIP, 0, len(values))
unique := make(map[netip.Addr]struct{})
addresses := make([]netip.Addr, 0, len(values))
for _, value := range values {
for candidate := range strings.SplitSeq(value, ",") {
candidate = strings.TrimSpace(candidate)
@@ -75,20 +76,27 @@ func normalizeLeaseVIPs(values []string) []LeaseVIP {
continue
}
unique[address] = struct{}{}
result = append(result, LeaseVIP{Index: len(result), Value: address})
addresses = append(addresses, address)
}
}
// Sorting keeps the annotation byte-identical however callers happen to order VIPs.
slices.SortFunc(addresses, netip.Addr.Compare)
result := make([]LeaseVIP, 0, len(addresses))
for _, address := range addresses {
result = append(result, LeaseVIP{Index: len(result), Value: address.String()})
}
return result
}
func parseLeaseVIP(value string) (string, error) {
func parseLeaseVIP(value string) (netip.Addr, error) {
address, err := netip.ParseAddr(value)
if err == nil {
return address.Unmap().String(), nil
return address.Unmap(), nil
}
prefix, prefixErr := netip.ParsePrefix(value)
if prefixErr != nil {
return "", fmt.Errorf("parse address %q: %w", value, err)
return netip.Addr{}, fmt.Errorf("parse address %q: %w", value, err)
}
return prefix.Addr().Unmap().String(), nil
return prefix.Addr().Unmap(), nil
}

View File

@@ -25,9 +25,43 @@ func TestWithLeaseVIPsEncodesVersionedInstanceOwnership(t *testing.T) {
t.Fatalf("Lease VIP metadata = %+v", value)
}
if len(value.VIPs) != 2 ||
value.VIPs[0] != (LeaseVIP{Index: 0, Value: "2001:db8::10"}) ||
value.VIPs[1] != (LeaseVIP{Index: 1, Value: "192.0.2.10"}) {
t.Fatalf("Lease VIPs = %v, want indexed VIPs in configuration order", value.VIPs)
value.VIPs[0] != (LeaseVIP{Index: 0, Value: "192.0.2.10"}) ||
value.VIPs[1] != (LeaseVIP{Index: 1, Value: "2001:db8::10"}) {
t.Fatalf("Lease VIPs = %v, want indexed VIPs in canonical address order", value.VIPs)
}
}
// The annotation is rewritten whenever a node starts campaigning, so the encoding
// has to be stable even when callers collect the same VIPs in a different order.
func TestWithLeaseVIPsIsIndependentOfInputOrder(t *testing.T) {
first, err := WithLeaseVIPs(nil, "release_a", 248, []string{
"2001:db8::10", "192.0.2.10", "10.0.0.2", "10.0.0.10",
})
if err != nil {
t.Fatalf("WithLeaseVIPs() error = %v", err)
}
second, err := WithLeaseVIPs(nil, "release_a", 248, []string{
"10.0.0.10", "192.0.2.10", "2001:db8::10", "10.0.0.2",
})
if err != nil {
t.Fatalf("WithLeaseVIPs() error = %v", err)
}
if first[LeaseVIPs] != second[LeaseVIPs] {
t.Fatalf("annotation changed with input order:\n%s\n%s", first[LeaseVIPs], second[LeaseVIPs])
}
value, err := ParseLeaseVIPs(first[LeaseVIPs])
if err != nil {
t.Fatalf("ParseLeaseVIPs() error = %v", err)
}
want := []string{"10.0.0.2", "10.0.0.10", "192.0.2.10", "2001:db8::10"}
if len(value.VIPs) != len(want) {
t.Fatalf("Lease VIPs = %v, want %v", value.VIPs, want)
}
for index, address := range want {
if value.VIPs[index] != (LeaseVIP{Index: index, Value: address}) {
t.Fatalf("Lease VIPs = %v, want %v", value.VIPs, want)
}
}
}