From b9bda8f2c1c336180e612b658d2e131ba1dd43de Mon Sep 17 00:00:00 2001 From: Dmitrii Andreev Date: Thu, 9 Oct 2025 12:58:55 +0300 Subject: [PATCH] client: enhance CNI allocation tests for IPv6 and dualstack scenarios Add multiple test cases to validate the behavior of CNI allocation when handling IPv6-only and dualstack configurations. The new tests ensure that the first address is selected correctly from multiple addresses across interfaces, maintaining consistent behavior with IPv4. This improves coverage for edge cases in CNI result processing and reinforces the recent fixes for IPv6 support. --- client/allocrunner/networking_cni_test.go | 130 +++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/client/allocrunner/networking_cni_test.go b/client/allocrunner/networking_cni_test.go index 3264f5c76..5b50b5845 100644 --- a/client/allocrunner/networking_cni_test.go +++ b/client/allocrunner/networking_cni_test.go @@ -521,11 +521,139 @@ func TestCNI_cniToAllocNet_IPv6Only(t *testing.T) { allocNet, err := c.cniToAllocNet(cniResult) must.NoError(t, err) must.NotNil(t, allocNet) - test.Eq(t, "", allocNet.Address) + test.Eq(t, "fd00:a110:c8::b", allocNet.Address) // fallback to IPv6 test.Eq(t, "fd00:a110:c8::b", allocNet.AddressIPv6) test.Eq(t, "eth0", allocNet.InterfaceName) } +// TestCNI_cniToAllocNet_IPv6Only_MultipleAddresses asserts that when a CNI result +// contains multiple IPv6 addresses on a single interface, the first address is selected. +// This ensures consistent behavior with the IPv4 case. +func TestCNI_cniToAllocNet_IPv6Only_MultipleAddresses(t *testing.T) { + ci.Parallel(t) + + cniResult := &cni.Result{ + Interfaces: map[string]*cni.Config{ + "eth0": { + Sandbox: "nomad-sandbox", + IPConfigs: []*cni.IPConfig{ + {IP: net.ParseIP("fd00:a110:c8::1")}, // first IPv6 - should be selected + {IP: net.ParseIP("fd00:a110:c8::2")}, // second IPv6 + {IP: net.ParseIP("fd00:a110:c8::3")}, // third IPv6 + }, + }, + }, + } + + c := &cniNetworkConfigurator{ + logger: testlog.HCLogger(t), + } + allocNet, err := c.cniToAllocNet(cniResult) + must.NoError(t, err) + must.NotNil(t, allocNet) + test.Eq(t, "fd00:a110:c8::1", allocNet.Address) // fallback to IPv6 + test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // should select first IPv6 address + test.Eq(t, "eth0", allocNet.InterfaceName) +} + +// TestCNI_cniToAllocNet_Dualstack_MultipleAddresses asserts that when a CNI result +// contains multiple IPv4 and IPv6 addresses, the first address of each type is selected. +func TestCNI_cniToAllocNet_Dualstack_MultipleAddresses(t *testing.T) { + ci.Parallel(t) + + cniResult := &cni.Result{ + Interfaces: map[string]*cni.Config{ + "eth0": { + Sandbox: "nomad-sandbox", + IPConfigs: []*cni.IPConfig{ + {IP: net.IPv4(192, 168, 1, 10)}, // first IPv4 - should be selected + {IP: net.ParseIP("fd00:a110:c8::1")}, // first IPv6 - should be selected + {IP: net.IPv4(192, 168, 1, 11)}, // second IPv4 + {IP: net.ParseIP("fd00:a110:c8::2")}, // second IPv6 + }, + }, + }, + } + + c := &cniNetworkConfigurator{ + logger: testlog.HCLogger(t), + } + allocNet, err := c.cniToAllocNet(cniResult) + must.NoError(t, err) + must.NotNil(t, allocNet) + test.Eq(t, "192.168.1.10", allocNet.Address) // should select first IPv4 address + test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // should select first IPv6 address + test.Eq(t, "eth0", allocNet.InterfaceName) +} + +// TestCNI_cniToAllocNet_MultipleInterfaces_IPv6First asserts that when multiple +// interfaces exist, the first interface (lexicographically) with an address is selected, +// even if it only has IPv6 and a later interface has IPv4. +func TestCNI_cniToAllocNet_MultipleInterfaces_IPv6First(t *testing.T) { + ci.Parallel(t) + + cniResult := &cni.Result{ + Interfaces: map[string]*cni.Config{ + "eth0": { + Sandbox: "nomad-sandbox", + IPConfigs: []*cni.IPConfig{ + {IP: net.ParseIP("fd00:a110:c8::1")}, // IPv6 only on first interface + }, + }, + "eth1": { + Sandbox: "nomad-sandbox", + IPConfigs: []*cni.IPConfig{ + {IP: net.IPv4(192, 168, 1, 10)}, // IPv4 on second interface + }, + }, + }, + } + + c := &cniNetworkConfigurator{ + logger: testlog.HCLogger(t), + } + allocNet, err := c.cniToAllocNet(cniResult) + must.NoError(t, err) + must.NotNil(t, allocNet) + test.Eq(t, "fd00:a110:c8::1", allocNet.Address) // fallback to IPv6 + test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // IPv6 from first interface + test.Eq(t, "eth0", allocNet.InterfaceName) // first interface should be selected +} + +// TestCNI_cniToAllocNet_MultipleInterfaces_IPv6OnMultiple asserts that when multiple +// interfaces each have IPv6 addresses, the first interface is selected. +func TestCNI_cniToAllocNet_MultipleInterfaces_IPv6OnMultiple(t *testing.T) { + ci.Parallel(t) + + cniResult := &cni.Result{ + Interfaces: map[string]*cni.Config{ + "eth0": { + Sandbox: "nomad-sandbox", + IPConfigs: []*cni.IPConfig{ + {IP: net.ParseIP("fd00:a110:c8::1")}, + {IP: net.ParseIP("fd00:a110:c8::2")}, // multiple on first interface + }, + }, + "eth1": { + Sandbox: "nomad-sandbox", + IPConfigs: []*cni.IPConfig{ + {IP: net.ParseIP("fd00:b220:c8::1")}, // different IPv6 on second interface + }, + }, + }, + } + + c := &cniNetworkConfigurator{ + logger: testlog.HCLogger(t), + } + allocNet, err := c.cniToAllocNet(cniResult) + must.NoError(t, err) + must.NotNil(t, allocNet) + test.Eq(t, "fd00:a110:c8::1", allocNet.Address) // fallback to IPv6 + test.Eq(t, "fd00:a110:c8::1", allocNet.AddressIPv6) // first address from first interface + test.Eq(t, "eth0", allocNet.InterfaceName) // first interface +} + func TestCNI_addCustomCNIArgs(t *testing.T) { ci.Parallel(t) cniArgs := map[string]string{