diff --git a/go.mod b/go.mod index cd84f9101..96a2a3d09 100644 --- a/go.mod +++ b/go.mod @@ -86,6 +86,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/viper v1.19.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect diff --git a/go.sum b/go.sum index d5c5eb3c8..34280963a 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,7 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= diff --git a/pkg/controllers/netpol/network_policy_controller_test.go b/pkg/controllers/netpol/network_policy_controller_test.go index cd187ee23..614f6d223 100644 --- a/pkg/controllers/netpol/network_policy_controller_test.go +++ b/pkg/controllers/netpol/network_policy_controller_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/coreos/go-iptables/iptables" - "github.com/vishvananda/netlink" netv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -758,51 +757,6 @@ func (ips *fakeIPSet) Name(name string) string { return name } -type fakeLocalLinkQuerier struct { - links []netlink.Link - addrs []*net.IPNet -} - -func newFakeLocalLinkQuerier(addrStrings []string) *fakeLocalLinkQuerier { - links := make([]netlink.Link, len(addrStrings)) - for idx := range addrStrings { - linkAttrs := netlink.LinkAttrs{ - Index: idx, - } - linkDevice := netlink.Device{LinkAttrs: linkAttrs} - links[idx] = &linkDevice - } - addrs := make([]*net.IPNet, len(addrStrings)) - for idx, addr := range addrStrings { - ip := net.ParseIP(addr) - var netMask net.IPMask - if ip.To4() != nil { - netMask = net.CIDRMask(24, 32) - } else { - netMask = net.CIDRMask(64, 128) - } - ipNet := &net.IPNet{ - IP: ip, - Mask: netMask, - } - addrs[idx] = ipNet - } - return &fakeLocalLinkQuerier{ - links: links, - addrs: addrs, - } -} - -func (f *fakeLocalLinkQuerier) LinkList() ([]netlink.Link, error) { - return f.links, nil -} - -func (f *fakeLocalLinkQuerier) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { - addrs := make([]netlink.Addr, 1) - addrs[0] = netlink.Addr{IPNet: f.addrs[link.Attrs().Index]} - return addrs, nil -} - func TestNetworkPolicyController(t *testing.T) { curHostname, _ := os.Hostname() testCases := []tNetPolConfigTestCase{ @@ -989,7 +943,7 @@ func TestNetworkPolicyController(t *testing.T) { }, } fakeNodeIPs := []string{"10.10.10.10", "2001:0db8:0042:0001:0000:0000:0000:0000"} - fakeLinkQuerier := newFakeLocalLinkQuerier(fakeNodeIPs) + fakeLinkQuerier := utils.NewFakeLocalLinkQuerier(fakeNodeIPs, nil) client := fake.NewSimpleClientset(&v1.NodeList{Items: []v1.Node{*newFakeNode("node", fakeNodeIPs)}}) _, podInformer, nsInformer, netpolInformer := newFakeInformersFromClient(client) for _, test := range testCases { diff --git a/pkg/utils/linux_routingtest.go b/pkg/utils/linux_routingtest.go new file mode 100644 index 000000000..1c83ce657 --- /dev/null +++ b/pkg/utils/linux_routingtest.go @@ -0,0 +1,78 @@ +package utils + +import ( + "fmt" + "net" + + "github.com/stretchr/testify/mock" + "github.com/vishvananda/netlink" +) + +type FakeLocalLinkQuerier struct { + links []netlink.Link + addrs []*net.IPNet +} + +func NewFakeLocalLinkQuerier(addrStrings []string, mtus []int) *FakeLocalLinkQuerier { + links := make([]netlink.Link, len(addrStrings)) + for idx := range addrStrings { + mtu := 1 + if idx < len(mtus) { + mtu = mtus[idx] + } + linkAttrs := netlink.LinkAttrs{ + Index: idx, + MTU: mtu, + } + linkDevice := netlink.Device{LinkAttrs: linkAttrs} + links[idx] = &linkDevice + } + addrs := make([]*net.IPNet, len(addrStrings)) + for idx, addr := range addrStrings { + ip := net.ParseIP(addr) + var netMask net.IPMask + if ip.To4() != nil { + //nolint:gomnd // Hardcoded value is used for testing purposes + netMask = net.CIDRMask(24, 32) + } else { + //nolint:gomnd // Hardcoded value is used for testing purposes + netMask = net.CIDRMask(64, 128) + } + ipNet := &net.IPNet{ + IP: ip, + Mask: netMask, + } + addrs[idx] = ipNet + } + return &FakeLocalLinkQuerier{ + links: links, + addrs: addrs, + } +} + +func (f *FakeLocalLinkQuerier) LinkList() ([]netlink.Link, error) { + return f.links, nil +} + +func (f *FakeLocalLinkQuerier) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { + addrs := make([]netlink.Addr, 1) + addrs[0] = netlink.Addr{IPNet: f.addrs[link.Attrs().Index]} + if link.Attrs().MTU == 0 { + return nil, fmt.Errorf("MTU was set to 0 to simulate an error") + } + return addrs, nil +} + +type MockLocalLinkQuerier struct { + mock.Mock +} + +func (m *MockLocalLinkQuerier) LinkList() ([]netlink.Link, error) { + args := m.Called() + return args.Get(0).([]netlink.Link), args.Error(1) +} + +func (m *MockLocalLinkQuerier) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { + args := m.Called(link, family) + return args.Get(0).([]netlink.Addr), args.Error(1) +} diff --git a/pkg/utils/node.go b/pkg/utils/node.go index 3f30cc354..b580de137 100644 --- a/pkg/utils/node.go +++ b/pkg/utils/node.go @@ -369,7 +369,7 @@ func GetNodeSubnet(nodeIP net.IP, linkQ LocalLinkQuerier) (net.IPNet, string, er for _, link := range links { addresses, err := linkQ.AddrList(link, netlink.FAMILY_ALL) if err != nil { - return net.IPNet{}, "", errors.New("failed to get list of addr") + return net.IPNet{}, "", errors.New("failed to get list of addrs") } for _, addr := range addresses { if addr.IPNet.IP.Equal(nodeIP) { diff --git a/pkg/utils/node_test.go b/pkg/utils/node_test.go index da3fe01a6..1fa9c8a78 100644 --- a/pkg/utils/node_test.go +++ b/pkg/utils/node_test.go @@ -9,6 +9,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/vishvananda/netlink" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -193,3 +195,688 @@ func Test_GetNodeIP(t *testing.T) { }) } } + +func Test_GetNodeIPv4Addrs(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + expected []net.IP + err error + }{ + { + "node with internal and external IPv4 addresses", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "192.168.1.1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("192.168.1.1")}, + nil, + }, + { + "node with only internal IPv4 address", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "2001:db8::2", + }, + }, + }, + }, + []net.IP{net.ParseIP("10.0.0.1")}, + nil, + }, + { + "node with only external IPv4 address", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeExternalIP, + Address: "192.168.1.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + []net.IP{net.ParseIP("192.168.1.1")}, + nil, + }, + { + "node with no IPv4 addresses", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{}, + }, + }, + []net.IP{}, + errors.New("error getting primary NodeIP: host IP unknown"), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + krNode, err := NewRemoteKRNode(testcase.node) + if err != nil { + if testcase.err == nil { + t.Fatalf("failed to create KRNode: %v", err) + } + assert.EqualError(t, err, testcase.err.Error()) + return + } + ipv4Addrs := krNode.GetNodeIPv4Addrs() + assert.Equal(t, testcase.expected, ipv4Addrs) + }) + } +} + +func Test_GetNodeIPv6Addrs(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + expected []net.IP + err error + }{ + { + "node with internal and external IPv4 addresses", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "192.168.1.1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + []net.IP{net.ParseIP("2001:db8::1")}, + nil, + }, + { + "node with only internal IPv4 address", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "2001:db8::2", + }, + }, + }, + }, + []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::2")}, + nil, + }, + { + "node with only external IPv4 address", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeExternalIP, + Address: "192.168.1.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + []net.IP{net.ParseIP("2001:db8::1")}, + nil, + }, + { + "node with no IPv4 addresses", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{}, + }, + }, + []net.IP{}, + errors.New("error getting primary NodeIP: host IP unknown"), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + krNode, err := NewRemoteKRNode(testcase.node) + if err != nil { + if testcase.err == nil { + t.Fatalf("failed to create KRNode: %v", err) + } + assert.EqualError(t, err, testcase.err.Error()) + return + } + ipv4Addrs := krNode.GetNodeIPv6Addrs() + assert.Equal(t, testcase.expected, ipv4Addrs) + }) + } +} + +func Test_FindBestIPv6NodeAddress(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + expected net.IP + }{ + { + "primary IP is already IPv6", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + net.ParseIP("2001:db8::1"), + }, + { + "internal IPv6 address available", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "2001:db8::2", + }, + }, + }, + }, + net.ParseIP("2001:db8::1"), + }, + { + "external IPv6 address available", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeExternalIP, + Address: "2001:db8::2", + }, + }, + }, + }, + net.ParseIP("2001:db8::2"), + }, + { + "no IPv6 address available", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + }, + }, + }, + nil, + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + krNode, err := NewRemoteKRNode(testcase.node) + if err != nil { + t.Fatalf("failed to create KRNode: %v", err) + } + ip := krNode.FindBestIPv6NodeAddress() + assert.Equal(t, testcase.expected, ip) + }) + } +} + +func Test_FindBestIPv4NodeAddress(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + expected net.IP + }{ + { + "primary IP is already IPv4", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + net.ParseIP("10.0.0.1"), + }, + { + "internal IPv4 address available", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeExternalIP, + Address: "192.168.1.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + net.ParseIP("10.0.0.1"), + }, + { + "external IPv4 address available", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeExternalIP, + Address: "192.168.1.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + net.ParseIP("192.168.1.1"), + }, + { + "no IPv4 address available", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + nil, + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + krNode, err := NewRemoteKRNode(testcase.node) + if err != nil { + t.Fatalf("failed to create KRNode: %v", err) + } + ip := krNode.FindBestIPv4NodeAddress() + assert.Equal(t, testcase.expected, ip) + }) + } +} + +func Test_NewKRNode(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + linkQ LocalLinkQuerier + enableIPv4 bool + enableIPv6 bool + expectedErr error + }{ + { + "valid node with IPv4 and IPv6", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + NewFakeLocalLinkQuerier([]string{"10.0.0.1", "2001:db8::1"}, nil), + true, + true, + nil, + }, + { + "node with no IPv4 address", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + NewFakeLocalLinkQuerier([]string{"2001:db8::1"}, nil), + true, + true, + errors.New("IPv4 was enabled, but no IPv4 address was found on the node"), + }, + { + "node with no IPv6 address", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + }, + }, + }, + NewFakeLocalLinkQuerier([]string{"10.0.0.1"}, nil), + true, + true, + errors.New("IPv6 was enabled, but no IPv6 address was found on the node"), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + _, err := NewKRNode(testcase.node, testcase.linkQ, testcase.enableIPv4, testcase.enableIPv6) + if testcase.expectedErr != nil { + assert.EqualError(t, err, testcase.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_NewRemoteKRNode(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + expectedErr error + }{ + { + "valid node with IPv4 and IPv6", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + nil, + }, + { + "node with no addresses", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{}, + }, + }, + errors.New("error getting primary NodeIP: host IP unknown"), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + _, err := NewRemoteKRNode(testcase.node) + if testcase.expectedErr != nil { + assert.EqualError(t, err, testcase.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func Test_GetNodeMTU(t *testing.T) { + testcases := []struct { + name string + node *apiv1.Node + linkQ LocalLinkQuerier + expectedMTU int + expectedErr error + }{ + { + "valid node with MTU", + &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-node", + }, + Status: apiv1.NodeStatus{ + Addresses: []apiv1.NodeAddress{ + { + Type: apiv1.NodeInternalIP, + Address: "10.0.0.1", + }, + { + Type: apiv1.NodeInternalIP, + Address: "2001:db8::1", + }, + }, + }, + }, + NewFakeLocalLinkQuerier([]string{"10.0.0.1", "2001:db8::1"}, []int{1480, 1500}), + 1480, + nil, + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + krNode, err := NewKRNode(testcase.node, testcase.linkQ, true, true) + if err != nil { + t.Fatalf("failed to create KRNode: %v", err) + } + mtu, err := krNode.GetNodeMTU() + if testcase.expectedErr != nil { + assert.EqualError(t, err, testcase.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, testcase.expectedMTU, mtu) + } + }) + } +} +func Test_GetNodeSubnet(t *testing.T) { + testcases := []struct { + name string + nodeIP net.IP + setupMock func(*MockLocalLinkQuerier) + expectedNet net.IPNet + expectedInt string + expectedErr error + }{ + { + "valid node with subnet", + net.ParseIP("10.0.0.1"), + func(myMock *MockLocalLinkQuerier) { + myMock.On("LinkList").Return( + []netlink.Link{&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "eth0"}}}, nil) + myMock.On("AddrList", mock.Anything, mock.Anything).Return( + []netlink.Addr{{IPNet: &net.IPNet{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)}}}, nil) + }, + net.IPNet{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)}, + "eth0", + nil, + }, + { + "node with no matching IP", + net.ParseIP("10.0.0.2"), + func(myMock *MockLocalLinkQuerier) { + myMock.On("LinkList").Return( + []netlink.Link{&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "eth0"}}}, nil) + myMock.On("AddrList", mock.Anything, mock.Anything).Return( + []netlink.Addr{{IPNet: &net.IPNet{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)}}}, nil) + }, + net.IPNet{}, + "", + errors.New("failed to find interface with specified node ip"), + }, + { + "error getting list of links", + net.ParseIP("10.0.0.1"), + func(myMock *MockLocalLinkQuerier) { + myMock.On("LinkList").Return([]netlink.Link{}, errors.New("failed to get list of links")) + }, + net.IPNet{}, + "", + errors.New("failed to get list of links"), + }, + { + "error getting addrs", + net.ParseIP("10.0.0.1"), + func(myMock *MockLocalLinkQuerier) { + myMock.On("LinkList").Return( + []netlink.Link{&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "eth0"}}}, nil) + myMock.On("AddrList", mock.Anything, mock.Anything).Return( + []netlink.Addr{}, errors.New("failed to get list of addrs")) + }, + net.IPNet{}, + "", + errors.New("failed to get list of addrs"), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + mockLinkQ := &MockLocalLinkQuerier{} + testcase.setupMock(mockLinkQ) + subnet, iface, err := GetNodeSubnet(testcase.nodeIP, mockLinkQ) + if testcase.expectedErr != nil { + assert.EqualError(t, err, testcase.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, testcase.expectedNet, subnet) + assert.Equal(t, testcase.expectedInt, iface) + } + }) + } +}