ip-algo: pod annotation > NodeConfig annotation > random
Build flock Image / build (push) Has been cancelled
Build flock Image / build (push) Has been cancelled
Add flock.fritzlab.net/ip-algo as a node-wide default via NodeConfig metadata.annotations. Pod-level annotation still wins. Empty, missing, or invalid input at either level falls through to the next; invalid values warn-log via the agent's slog. Both unset → fully random IID (unchanged baseline). ParseAnnotations no longer touches ip-algo; ResolveIPAlgo handles the full precedence chain, called from PodHandler.Add with the cached NodeConfig's annotations and the agent logger. Tests: 9 new TestResolveIPAlgo_* cases covering pod-wins, all fall-through paths, both-absent, nil node map, whitespace, and duplicate-as-invalid. Fuzz target rebuilt without ip-algo input space (now exercised by ResolveIPAlgo unit tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -174,32 +174,105 @@ func TestParseAnnotations_BoolCaseInsensitive(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_IPAlgo(t *testing.T) {
|
||||
a, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ip-algo": "namespace,pod,image",
|
||||
}, BuiltinFamilyDefaults())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
// ResolveIPAlgo: precedence is pod → node → nil. Empty / missing / invalid
|
||||
// at any level falls through to the next under the relaxed user-defined rule
|
||||
// "all three mean unset".
|
||||
|
||||
func TestResolveIPAlgo_PodWins(t *testing.T) {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: "namespace,pod"}
|
||||
node := map[string]string{annotationPrefix + annIPAlgo: "image"}
|
||||
got := ResolveIPAlgo(pod, node, nil)
|
||||
want := []embed.Field{embed.FieldNamespace, embed.FieldPod}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
want := []embed.Field{embed.FieldNamespace, embed.FieldPod, embed.FieldImage}
|
||||
if len(a.IPAlgo) != len(want) {
|
||||
t.Fatalf("ip-algo len=%d, want %d", len(a.IPAlgo), len(want))
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_PodAbsentFallsToNode(t *testing.T) {
|
||||
node := map[string]string{annotationPrefix + annIPAlgo: "image"}
|
||||
got := ResolveIPAlgo(nil, node, nil)
|
||||
want := []embed.Field{embed.FieldImage}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if a.IPAlgo[i] != want[i] {
|
||||
t.Fatalf("ip-algo[%d]=%s, want %s", i, a.IPAlgo[i], want[i])
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_PodEmptyFallsToNode(t *testing.T) {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: ""}
|
||||
node := map[string]string{annotationPrefix + annIPAlgo: "image"}
|
||||
got := ResolveIPAlgo(pod, node, nil)
|
||||
want := []embed.Field{embed.FieldImage}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_PodInvalidFallsToNode(t *testing.T) {
|
||||
for _, podVal := range []string{"namespace,bogus", "ns", ",", "namespace,namespace"} {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: podVal}
|
||||
node := map[string]string{annotationPrefix + annIPAlgo: "pod"}
|
||||
got := ResolveIPAlgo(pod, node, nil)
|
||||
want := []embed.Field{embed.FieldPod}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("podVal=%q: got %v, want %v", podVal, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAnnotations_IPAlgo_Unknown(t *testing.T) {
|
||||
if _, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "ip-algo": "namespace,foo",
|
||||
}, BuiltinFamilyDefaults()); err == nil {
|
||||
t.Fatalf("expected unknown-field error")
|
||||
func TestResolveIPAlgo_BothInvalidReturnsNil(t *testing.T) {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: "bogus"}
|
||||
node := map[string]string{annotationPrefix + annIPAlgo: "also-bogus"}
|
||||
if got := ResolveIPAlgo(pod, node, nil); got != nil {
|
||||
t.Fatalf("got %v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_BothAbsentReturnsNil(t *testing.T) {
|
||||
if got := ResolveIPAlgo(nil, nil, nil); got != nil {
|
||||
t.Fatalf("got %v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_NilNodeMap(t *testing.T) {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: "image"}
|
||||
got := ResolveIPAlgo(pod, nil, nil)
|
||||
want := []embed.Field{embed.FieldImage}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_Whitespace(t *testing.T) {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: " namespace , pod "}
|
||||
got := ResolveIPAlgo(pod, nil, nil)
|
||||
want := []embed.Field{embed.FieldNamespace, embed.FieldPod}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPAlgo_DuplicateInvalidates(t *testing.T) {
|
||||
pod := map[string]string{annotationPrefix + annIPAlgo: "pod,pod"}
|
||||
node := map[string]string{annotationPrefix + annIPAlgo: "namespace"}
|
||||
got := ResolveIPAlgo(pod, node, nil)
|
||||
want := []embed.Field{embed.FieldNamespace}
|
||||
if !equalFields(got, want) {
|
||||
t.Fatalf("got %v, want %v (duplicate must collapse to invalid)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func equalFields(a, b []embed.Field) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestParseAnnotations_CIDR(t *testing.T) {
|
||||
a, err := ParseAnnotations(map[string]string{
|
||||
annotationPrefix + "cidr6": "2602:817:3000:f001::/64, 2602:817:3000:f002::/64",
|
||||
|
||||
Reference in New Issue
Block a user