diff --git a/hack/update-codegen-dockerized.sh b/hack/update-codegen-dockerized.sh index cd5232fa825..d16f298c6b5 100755 --- a/hack/update-codegen-dockerized.sh +++ b/hack/update-codegen-dockerized.sh @@ -67,6 +67,7 @@ MOCKGEN_TARGETS=( "pkg/agent/querier AgentQuerier testing" "pkg/agent/route Interface testing" "pkg/agent/ipassigner IPAssigner testing" + "pkg/agent/ipassigner/responder Responder testing" "pkg/agent/secondarynetwork/podwatch InterfaceConfigurator,IPAMAllocator testing" "pkg/agent/servicecidr Interface testing" "pkg/agent/util/ipset Interface testing" diff --git a/pkg/agent/ipassigner/ip_assigner_linux.go b/pkg/agent/ipassigner/ip_assigner_linux.go index 6256bae61b2..1007adc5796 100644 --- a/pkg/agent/ipassigner/ip_assigner_linux.go +++ b/pkg/agent/ipassigner/ip_assigner_linux.go @@ -35,6 +35,21 @@ import ( crdv1b1 "antrea.io/antrea/pkg/apis/crd/v1beta1" ) +var ( + netlinkAdd = netlink.LinkAdd + netlinkDel = netlink.LinkDel + ensureRPF = util.EnsureRPFilterOnInterface + netlinkSetUp = netlink.LinkSetUp + netInterfaceByName = net.InterfaceByName + netlinkAddrAdd = netlink.AddrAdd + netlinkAddrDel = netlink.AddrDel + netlinkAddrList = netlink.AddrList +) + +type advertiseArpNdp func(a *assignee, ip net.IP) + +var advertiseResponder advertiseArpNdp = (*assignee).advertise + // VLAN interfaces created by antrea-agent will be named with the prefix. // For example, when VLAN ID is 10, the name will be antrea-ext.10. // It can be used to determine whether it's safe to delete an interface when it's no longer used. @@ -79,7 +94,7 @@ func (as *assignee) deletable() bool { } func (as *assignee) destroy() error { - if err := netlink.LinkDel(as.link); err != nil { + if err := netlinkDel(as.link); err != nil { return fmt.Errorf("error deleting interface %v: %w", as.link, err) } return nil @@ -89,7 +104,7 @@ func (as *assignee) assign(ip net.IP, subnetInfo *crdv1b1.SubnetInfo) error { // If there is a real link, add the IP to its address list. if as.link != nil { addr := getIPNet(ip, subnetInfo) - if err := netlink.AddrAdd(as.link, &netlink.Addr{IPNet: addr}); err != nil { + if err := netlinkAddrAdd(as.link, &netlink.Addr{IPNet: addr}); err != nil { if !errors.Is(err, unix.EEXIST) { return fmt.Errorf("failed to add IP %v to interface %s: %v", addr, as.link.Attrs().Name, err) } else { @@ -111,7 +126,7 @@ func (as *assignee) assign(ip net.IP, subnetInfo *crdv1b1.SubnetInfo) error { } } // Always advertise the IP when the IP is newly assigned to this Node. - as.advertise(ip) + advertiseResponder(as, ip) as.ips.Insert(ip.String()) return nil } @@ -134,7 +149,7 @@ func (as *assignee) unassign(ip net.IP, subnetInfo *crdv1b1.SubnetInfo) error { // If there is a real link, delete the IP from its address list. if as.link != nil { addr := getIPNet(ip, subnetInfo) - if err := netlink.AddrDel(as.link, &netlink.Addr{IPNet: addr}); err != nil { + if err := netlinkAddrDel(as.link, &netlink.Addr{IPNet: addr}); err != nil { if !errors.Is(err, unix.EADDRNOTAVAIL) { return fmt.Errorf("failed to delete IP %v from interface %s: %v", ip, as.link.Attrs().Name, err) } else { @@ -171,7 +186,7 @@ func (as *assignee) getVLANID() (int, bool) { func (as *assignee) loadIPAddresses() (map[string]*crdv1b1.SubnetInfo, error) { assignedIPs := map[string]*crdv1b1.SubnetInfo{} - addresses, err := netlink.AddrList(as.link, netlink.FAMILY_ALL) + addresses, err := netlinkAddrList(as.link, netlink.FAMILY_ALL) if err != nil { return nil, err } @@ -362,7 +377,7 @@ func (a *ipAssigner) AssignIP(ip string, subnetInfo *crdv1b1.SubnetInfo, forceAd if crdv1b1.CompareSubnetInfo(subnetInfo, oldSubnetInfo, true) { klog.V(2).InfoS("The IP is already assigned", "ip", ip) if forceAdvertise { - as.advertise(parsedIP) + advertiseResponder(as, parsedIP) } return false, nil } @@ -497,7 +512,7 @@ func (a *ipAssigner) getAssignee(subnetInfo *crdv1b1.SubnetInfo, createIfNotExis }, VlanId: int(subnetInfo.VLAN), } - if err := netlink.LinkAdd(vlan); err != nil { + if err := netlinkAdd(vlan); err != nil { if !errors.Is(err, unix.EEXIST) { return nil, fmt.Errorf("error creating VLAN sub-interface for VLAN %d", subnetInfo.VLAN) } @@ -505,7 +520,7 @@ func (a *ipAssigner) getAssignee(subnetInfo *crdv1b1.SubnetInfo, createIfNotExis // Loose mode is needed because incoming traffic received on the interface is expected to be received on the parent // external interface when looking up the main table. To make it look up the custom table, we will need to restore // the mark on the reply traffic and turn on src_valid_mark on this interface, which is more complicated. - if err := util.EnsureRPFilterOnInterface(name, 2); err != nil { + if err := ensureRPF(name, 2); err != nil { return nil, err } as, err := a.addVLANAssignee(vlan, subnetInfo.VLAN) @@ -516,10 +531,10 @@ func (a *ipAssigner) getAssignee(subnetInfo *crdv1b1.SubnetInfo, createIfNotExis } func (a *ipAssigner) addVLANAssignee(link netlink.Link, vlan int32) (*assignee, error) { - if err := netlink.LinkSetUp(link); err != nil { + if err := netlinkSetUp(link); err != nil { return nil, fmt.Errorf("error setting up interface %v", link) } - iface, err := net.InterfaceByName(link.Attrs().Name) + iface, err := netInterfaceByName(link.Attrs().Name) if err != nil { return nil, err } diff --git a/pkg/agent/ipassigner/ip_assigner_linux_test.go b/pkg/agent/ipassigner/ip_assigner_linux_test.go new file mode 100644 index 00000000000..4216971150e --- /dev/null +++ b/pkg/agent/ipassigner/ip_assigner_linux_test.go @@ -0,0 +1,1041 @@ +// Copyright 2024 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ipassigner + +import ( + "fmt" + "net" + "reflect" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vishvananda/netlink" + gomock "go.uber.org/mock/gomock" + "k8s.io/apimachinery/pkg/util/sets" + + respondertest "antrea.io/antrea/pkg/agent/ipassigner/responder/testing" + netlinktest "antrea.io/antrea/pkg/agent/util/netlink/testing" + crdv1b1 "antrea.io/antrea/pkg/apis/crd/v1beta1" +) + +func ensureRPFInt(name string, y int) error { + return nil +} + +func dummyAdvertise(a *assignee, ip net.IP) { +} + +func DummyInterfaceByName(name string) (*net.Interface, error) { + return &net.Interface{ + Index: 0, + MTU: 1500, + Name: "eth0", + HardwareAddr: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, + }, nil +} + +func newFakeNetworkInterface() *net.Interface { + return &net.Interface{ + Index: 0, + MTU: 1500, + Name: "eth0", + HardwareAddr: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, + } +} + +type dummyDeviceMock struct { + addedIPs []net.IPNet + vlanID int +} + +func (d *dummyDeviceMock) Attrs() *netlink.LinkAttrs { + if d.vlanID == 0 { + return &netlink.LinkAttrs{Name: "antrea-dummy0"} + } + return &netlink.LinkAttrs{Name: fmt.Sprintf("antrea-ext.%d", d.vlanID)} +} + +func (d *dummyDeviceMock) AddrAdd(addr *netlink.Addr) error { + ipNet := net.IPNet{ + IP: addr.IPNet.IP, + Mask: net.CIDRMask(32, 32), + } + d.addedIPs = append(d.addedIPs, ipNet) + return nil +} + +func (d *dummyDeviceMock) Type() string { + return "dummy" +} + +func TestIPAssigner_AssignIP(t *testing.T) { + var err error + var subnetInfo *crdv1b1.SubnetInfo + + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + mockNetlink := netlinktest.NewMockInterface(controller) + + tests := []struct { + name string + ip string + vlanid int + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + expectedCalls func(mockNetlink *netlinktest.MockInterface) + }{ + { + name: "Invalid IP", + ip: "abc", + vlanid: 0, + assignedIPs: make(map[string]*crdv1b1.SubnetInfo), + ips: sets.New[string](), + expectedError: true, + expectedAssignedIPs: make(map[string]*crdv1b1.SubnetInfo), + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + }, + }, + { + name: "Assign new IP", + ip: "2.1.1.1", + vlanid: 0, + assignedIPs: make(map[string]*crdv1b1.SubnetInfo), + ips: sets.New[string](), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().AddIP(net.ParseIP("2.1.1.1")).Return(nil) + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "2.1.1.1" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + mockNetlink.EXPECT().AddrAdd(&dummyDeviceMock{}, addr).Return(nil) + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + { + name: "Assign existing IP", + ip: "2.1.1.1", + vlanid: 0, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + }, + ips: sets.New[string]("2.1.1.1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + }, + }, + { + name: "Add more IP", + ip: "2.2.2.1", + vlanid: 0, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + }, + ips: sets.New[string]("2.1.1.1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().AddIP(net.ParseIP("2.2.2.1")).Return(nil) + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipNet := &net.IPNet{ + IP: net.ParseIP("2.2.2.1"), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + mockNetlink.EXPECT().AddrAdd(&dummyDeviceMock{}, addr).Return(nil) + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + { + name: "Assign IPv6", + ip: "2001:db8::1", + vlanid: 0, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + }, + ips: sets.New[string]("2.1.1.1", "2.2.2.1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().AddIP(net.ParseIP("2001:db8::1")).Return(nil) + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipNet := &net.IPNet{ + IP: net.ParseIP("2001:db8::1"), + Mask: net.CIDRMask(128, 128), + } + addr := &netlink.Addr{IPNet: ipNet} + mockNetlink.EXPECT().AddrAdd(&dummyDeviceMock{}, addr).Return(nil) + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.vlanid == 0 { + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: tt.ips, + }, + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + a.defaultAssignee.link = &dummyDeviceMock{} + a.defaultAssignee.arpResponder = mockResponder + a.defaultAssignee.ndpResponder = mockResponder + tt.expectFunc(mockResponder) + tt.expectedCalls(mockNetlink) + + originalAdvertiseResponder := advertiseResponder + advertiseResponder = dummyAdvertise + defer func() { + advertiseResponder = originalAdvertiseResponder + }() + + _, err = a.AssignIP(tt.ip, subnetInfo, false) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.expectedAssignedIPs, a.assignedIPs, "Assigned IPs don't match") + } + }) + } +} + +func TestIPAssigner_AssignIPVlan(t *testing.T) { + var err error + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + var subnetInfo *crdv1b1.SubnetInfo + mockNetlink := netlinktest.NewMockInterface(controller) + + tests := []struct { + name string + ip string + vlanid int + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + expectedCalls func(mockNetlink *netlinktest.MockInterface) + }{ + { + name: "Assign IPv4 vlan 12", + ip: "4.4.4.2", + vlanid: 12, + ips: sets.New[string](), + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + "4.4.4.2": {PrefixLength: 32, + VLAN: 12, + }, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "4.4.4.2" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + + vlan := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.12", + }, + VlanId: 12, + } + mockNetlink.EXPECT().LinkSetUp(vlan).Return(nil) + mockNetlink.EXPECT().AddrAdd(vlan, addr).Return(nil) + mockNetlink.EXPECT().LinkAdd(vlan).Return(nil) + netlinkAdd = mockNetlink.LinkAdd + netlinkSetUp = mockNetlink.LinkSetUp + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + { + name: "Assign IPv4 vlan 13", + ip: "5.5.5.2", + vlanid: 13, + ips: sets.New[string](), + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + "4.4.4.2": {PrefixLength: 32, + VLAN: 12, + }, + }, + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + "4.4.4.2": {PrefixLength: 32, + VLAN: 12, + }, + "5.5.5.2": {PrefixLength: 32, + VLAN: 13, + }, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "5.5.5.2" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + vlan := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.13", + }, + VlanId: 13, + } + mockNetlink.EXPECT().AddrAdd(vlan, addr).Return(nil) + mockNetlink.EXPECT().LinkSetUp(vlan).Return(nil) + mockNetlink.EXPECT().LinkAdd(vlan).Return(nil) + netlinkAdd = mockNetlink.LinkAdd + netlinkSetUp = mockNetlink.LinkSetUp + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + + vlanAssignees: map[int32]*assignee{}, + + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + + subnetInfo := &crdv1b1.SubnetInfo{ + PrefixLength: 32, + VLAN: int32(tt.vlanid), + } + + tt.expectFunc(mockResponder) + tt.expectedCalls(mockNetlink) + + ensRpfFunc := ensureRPF + defer func() { ensureRPF = ensRpfFunc }() + ensureRPF = ensureRPFInt + + netInterfaceByNameFunc := netInterfaceByName + defer func() { netInterfaceByName = netInterfaceByNameFunc }() + netInterfaceByName = DummyInterfaceByName + + originalAdvertiseResponder := advertiseResponder + advertiseResponder = dummyAdvertise + defer func() { + advertiseResponder = originalAdvertiseResponder + }() + + _, err = a.AssignIP(tt.ip, subnetInfo, false) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.expectedAssignedIPs, a.assignedIPs, "Assigned IPs don't match") + }) + } +} + +func TestIPAssigner_UnassignIP(t *testing.T) { + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + var subnetInfo *crdv1b1.SubnetInfo + mockNetlink := netlinktest.NewMockInterface(controller) + + tests := []struct { + name string + ip string + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + expectedCalls func(mockNetlink *netlinktest.MockInterface) + }{ + { + name: "Invalid IP", + ip: "abc", + assignedIPs: make(map[string]*crdv1b1.SubnetInfo), + ips: sets.New[string](), + expectedError: true, + expectedAssignedIPs: make(map[string]*crdv1b1.SubnetInfo), + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + }, + }, + { + name: "UnassignIP not assigned", + ip: "3.3.3.2", + assignedIPs: make(map[string]*crdv1b1.SubnetInfo), + ips: sets.New[string](), + expectedAssignedIPs: make(map[string]*crdv1b1.SubnetInfo), + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + }, + }, + { + name: "Unassign IPv4", + ip: "2.1.1.1", + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + }, + ips: sets.New[string]("2.1.1.1", "2.2.2.1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.2.2.1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().RemoveIP(net.ParseIP("2.1.1.1")).Return(nil) + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + + ipNet := &net.IPNet{ + IP: net.ParseIP("2.1.1.1"), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + mockNetlink.EXPECT().AddrDel(&dummyDeviceMock{}, addr).Return(nil) + netlinkAddrDel = mockNetlink.AddrDel + }, + }, + { + name: "Unassign IPv6", + ip: "2001:db8::1", + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + ips: sets.New[string]("2.2.2.1", "2001:db8::1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.2.2.1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().RemoveIP(net.ParseIP("2001:db8::1")).Return(nil) + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipNet := &net.IPNet{ + IP: net.ParseIP("2001:db8::1"), + Mask: net.CIDRMask(128, 128), + } + addr := &netlink.Addr{IPNet: ipNet} + mockNetlink.EXPECT().AddrDel(&dummyDeviceMock{}, addr).Return(nil) + netlinkAddrDel = mockNetlink.AddrDel + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: tt.ips, + }, + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + a.defaultAssignee.link = &dummyDeviceMock{} + a.defaultAssignee.arpResponder = mockResponder + a.defaultAssignee.ndpResponder = mockResponder + tt.expectFunc(mockResponder) + tt.expectedCalls(mockNetlink) + + originalAdvertiseResponder := advertiseResponder + advertiseResponder = dummyAdvertise + defer func() { + advertiseResponder = originalAdvertiseResponder + }() + + _, err := a.UnassignIP(tt.ip) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.expectedAssignedIPs, a.assignedIPs, "Unassigned IPs don't match") + }) + } +} + +func TestIPAssigner_UnassignIPVlan(t *testing.T) { + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + var subnetInfo *crdv1b1.SubnetInfo + mockNetlink := netlinktest.NewMockInterface(controller) + + tests := []struct { + name string + ip string + vlanid int + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + vlanAssignees map[int32]*assignee + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + expectedCalls func(mockNetlink *netlinktest.MockInterface) + }{ + { + name: "Unassign IPv4 Vlan IP", + ip: "4.4.4.2", + vlanid: 12, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "4.4.4.2": {PrefixLength: 32, + VLAN: 12, + }, + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + + ips: sets.New[string]("4.4.4.2", "2.1.1.1", "2.2.2.1", "2001:db8::1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "4.4.4.2" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + vlan := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.12", + }, + VlanId: 12, + } + mockNetlink.EXPECT().AddrDel(vlan, addr).Return(nil) + mockNetlink.EXPECT().LinkDel(vlan).Return(nil) + netlinkDel = mockNetlink.LinkDel + netlinkAddrDel = mockNetlink.AddrDel + }, + }, + { + name: "Unassign IPv4 Vlan IP-2", + ip: "5.5.5.2", + vlanid: 13, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + "5.5.5.2": {PrefixLength: 32, + VLAN: 13, + }, + }, + ips: sets.New[string]("5.5.5.2", "2.2.2.1", "2001:db8::1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "5.5.5.2" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + vlan := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.13", + }, + VlanId: 13, + } + mockNetlink.EXPECT().AddrDel(vlan, addr).Return(nil) + mockNetlink.EXPECT().LinkDel(vlan).Return(nil) + netlinkDel = mockNetlink.LinkDel + netlinkAddrDel = mockNetlink.AddrDel + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + + vlanAssignees: map[int32]*assignee{ + 12: { + link: &netlink.Vlan{LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.12", + }, + VlanId: 12}, + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + 13: { + link: &netlink.Vlan{LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.13", + }, + VlanId: 13}, + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + }, + + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + + tt.expectFunc(mockResponder) + tt.expectedCalls(mockNetlink) + + ensRpfFunc := ensureRPF + defer func() { ensureRPF = ensRpfFunc }() + ensureRPF = ensureRPFInt + + netInterfaceByNameFunc := netInterfaceByName + defer func() { netInterfaceByName = netInterfaceByNameFunc }() + netInterfaceByName = DummyInterfaceByName + + _, err := a.UnassignIP(tt.ip) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.expectedAssignedIPs, a.assignedIPs, "Unassigned IPs don't match") + }) + } + +} + +func TestIPAssigner_AssignedIPs(t *testing.T) { + var subnetInfo *crdv1b1.SubnetInfo + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + //ips: tt.ips, + }, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "3.3.3.1": subnetInfo, + }, + mutex: sync.RWMutex{}, + } + a.defaultAssignee.link = &dummyDeviceMock{} + a.defaultAssignee.arpResponder = mockResponder + a.defaultAssignee.ndpResponder = mockResponder + + originalAdvertiseResponder := advertiseResponder + advertiseResponder = dummyAdvertise + defer func() { + advertiseResponder = originalAdvertiseResponder + }() + + ips := a.AssignedIPs() + + expectedIPs := map[string]*crdv1b1.SubnetInfo{ + "2.1.1.1": subnetInfo, + "3.3.3.1": subnetInfo, + } + if !reflect.DeepEqual(a.assignedIPs, expectedIPs) { + t.Errorf("expected IPs: %v, but got: %v", expectedIPs, ips) + } +} + +func TestIPAssigner_AssignedIPsVlan(t *testing.T) { + var subnetInfo *crdv1b1.SubnetInfo + tests := []struct { + name string + ip string + vlanid int + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + vlanAssignees map[int32]*assignee + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + }{ + { + name: "AssignedIPsVlan IPv4", + ip: "4.4.4.2", + vlanid: 12, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "4.4.4.2": {PrefixLength: 24, + VLAN: 12, + }, + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + + ips: sets.New[string]("4.4.4.2", "2.1.1.1", "2.2.2.1", "2001:db8::1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "4.4.4.2": {PrefixLength: 24, + VLAN: 12, + }, + "2.1.1.1": subnetInfo, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + }, + { + name: "Unassign IPv4 Vlan IP-2", + ip: "5.5.5.2", + vlanid: 13, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + "5.5.5.2": {PrefixLength: 24, + VLAN: 13, + }, + }, + ips: sets.New[string]("5.5.5.2", "2.2.2.1", "2001:db8::1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "5.5.5.2": {PrefixLength: 24, + VLAN: 13, + }, + "2.2.2.1": subnetInfo, + "2001:db8::1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + + vlanAssignees: map[int32]*assignee{ + 12: { + link: &netlink.Vlan{LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.12", + }, + VlanId: 12}, + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + 13: { + link: &netlink.Vlan{LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.13", + }, + VlanId: 13}, + logicalInterface: newFakeNetworkInterface(), + ips: sets.New[string](), + }, + }, + + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + + ips := a.AssignedIPs() + + if !reflect.DeepEqual(a.assignedIPs, tt.expectedAssignedIPs) { + t.Errorf("expected IPs: %v, but got: %v", tt.expectedAssignedIPs, ips) + } + }) + } +} + +func TestIPAssigner_InitIPs(t *testing.T) { + var err error + var subnetInfo *crdv1b1.SubnetInfo + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + mockNetlink := netlinktest.NewMockInterface(controller) + + tests := []struct { + name string + desiredIPs map[string]*crdv1b1.SubnetInfo + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + expectedCalls func(mockNetlink *netlinktest.MockInterface) + }{ + { + + name: "InitIPs with new IP", + desiredIPs: map[string]*crdv1b1.SubnetInfo{ + "8.8.8.1": subnetInfo, + }, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "192.168.1.5": subnetInfo, + "2.1.1.3": subnetInfo, + }, + ips: sets.New[string]("8.8.8.1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "8.8.8.1": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().AddIP(net.ParseIP("8.8.8.1")).Return(nil) + + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "8.8.8.1" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + mockNetlink.EXPECT().AddrAdd(&dummyDeviceMock{}, addr).Return(nil) + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + { + name: "InitIPs with new and old ip", + desiredIPs: map[string]*crdv1b1.SubnetInfo{ + "8.8.8.1": subnetInfo, + "8.8.8.2": subnetInfo, + }, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "192.168.1.5": subnetInfo, + "192.168.1.105": subnetInfo, + "2.1.1.3": subnetInfo, + }, + ips: sets.New[string]("8.8.8.1", "8.8.8.2"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "8.8.8.1": subnetInfo, + "8.8.8.2": subnetInfo, + }, + expectFunc: func(mock *respondertest.MockResponder) { + mock.EXPECT().AddIP(net.ParseIP("8.8.8.1")).Return(nil) + mock.EXPECT().AddIP(net.ParseIP("8.8.8.2")).Return(nil) + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "8.8.8.1" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + ipAddress1 := "8.8.8.2" + ipNet1 := &net.IPNet{ + IP: net.ParseIP(ipAddress1), + Mask: net.CIDRMask(32, 32), + } + addr1 := &netlink.Addr{IPNet: ipNet1} + mockNetlink.EXPECT().AddrAdd(&dummyDeviceMock{}, addr).Return(nil) + mockNetlink.EXPECT().AddrAdd(&dummyDeviceMock{}, addr1).Return(nil) + netlinkAddrAdd = mockNetlink.AddrAdd + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: tt.ips, + }, + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + a.defaultAssignee.link = &dummyDeviceMock{} + a.defaultAssignee.arpResponder = mockResponder + a.defaultAssignee.ndpResponder = mockResponder + tt.expectFunc(mockResponder) + tt.expectedCalls(mockNetlink) + + netlinkAddrDel = mockNetlink.AddrDel + + originalAdvertiseResponder := advertiseResponder + advertiseResponder = dummyAdvertise + defer func() { + advertiseResponder = originalAdvertiseResponder + }() + + err = a.InitIPs(tt.desiredIPs) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestIPAssigner_InitIPsVlan(t *testing.T) { + var err error + var subnetInfo *crdv1b1.SubnetInfo + controller := gomock.NewController(t) + mockResponder := respondertest.NewMockResponder(controller) + mockNetlink := netlinktest.NewMockInterface(controller) + + tests := []struct { + name string + vlanid int + vlanAssignees map[int32]*assignee + desiredIPs map[string]*crdv1b1.SubnetInfo + assignedIPs map[string]*crdv1b1.SubnetInfo + ips sets.Set[string] + expectedError bool + expectedAssignedIPs map[string]*crdv1b1.SubnetInfo + expectFunc func(mock *respondertest.MockResponder) + expectedCalls func(mockNetlink *netlinktest.MockInterface) + }{ + { + name: "InitIPs with vlan IP", + vlanid: 12, + desiredIPs: map[string]*crdv1b1.SubnetInfo{ + "8.8.8.1": {PrefixLength: 32, + VLAN: 12, + }, + }, + assignedIPs: map[string]*crdv1b1.SubnetInfo{ + "192.168.1.5": subnetInfo, + "2.1.1.3": subnetInfo, + }, + ips: sets.New[string]("8.8.8.1"), + expectedAssignedIPs: map[string]*crdv1b1.SubnetInfo{ + "8.8.8.1": {PrefixLength: 24, + VLAN: 12, + }, + }, + expectFunc: func(mock *respondertest.MockResponder) { + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterface) { + ipAddress := "8.8.8.1" + ipNet := &net.IPNet{ + IP: net.ParseIP(ipAddress), + Mask: net.CIDRMask(32, 32), + } + addr := &netlink.Addr{IPNet: ipNet} + vlan := &netlink.Vlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: "antrea-ext.12", + }, + VlanId: 12, + } + mockNetlink.EXPECT().AddrAdd(vlan, addr).Return(nil) + mockNetlink.EXPECT().AddrList(&dummyDeviceMock{}, netlink.FAMILY_ALL).Return(nil, nil) + mockNetlink.EXPECT().LinkSetUp(vlan).Return(nil) + mockNetlink.EXPECT().LinkAdd(vlan).Return(nil) + netlinkSetUp = mockNetlink.LinkSetUp + netlinkAddrAdd = mockNetlink.AddrAdd + netlinkAddrList = mockNetlink.AddrList + netlinkAdd = mockNetlink.LinkAdd + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &ipAssigner{ + externalInterface: newFakeNetworkInterface(), + defaultAssignee: &assignee{ + logicalInterface: newFakeNetworkInterface(), + ips: tt.ips, + }, + vlanAssignees: map[int32]*assignee{}, + assignedIPs: tt.assignedIPs, + mutex: sync.RWMutex{}, + } + a.defaultAssignee.link = &dummyDeviceMock{} + a.defaultAssignee.arpResponder = mockResponder + a.defaultAssignee.ndpResponder = mockResponder + tt.expectFunc(mockResponder) + tt.expectedCalls(mockNetlink) + + originalAdvertiseResponder := advertiseResponder + advertiseResponder = dummyAdvertise + defer func() { + advertiseResponder = originalAdvertiseResponder + }() + + ensRpfFunc := ensureRPF + defer func() { ensureRPF = ensRpfFunc }() + ensureRPF = ensureRPFInt + + netInterfaceByNameFunc := netInterfaceByName + defer func() { netInterfaceByName = netInterfaceByNameFunc }() + netInterfaceByName = DummyInterfaceByName + + err = a.InitIPs(tt.desiredIPs) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/agent/ipassigner/responder/testing/mock_responder.go b/pkg/agent/ipassigner/responder/testing/mock_responder.go new file mode 100644 index 00000000000..600fa63c491 --- /dev/null +++ b/pkg/agent/ipassigner/responder/testing/mock_responder.go @@ -0,0 +1,108 @@ +// Copyright 2023 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: antrea.io/antrea/pkg/agent/ipassigner/responder (interfaces: Responder) +// +// Generated by this command: +// +// mockgen -copyright_file hack/boilerplate/license_header.raw.txt -destination pkg/agent/ipassigner/responder/testing/mock_responder.go -package testing antrea.io/antrea/pkg/agent/ipassigner/responder Responder +// +// Package testing is a generated GoMock package. +package testing + +import ( + net "net" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockResponder is a mock of Responder interface. +type MockResponder struct { + ctrl *gomock.Controller + recorder *MockResponderMockRecorder +} + +// MockResponderMockRecorder is the mock recorder for MockResponder. +type MockResponderMockRecorder struct { + mock *MockResponder +} + +// NewMockResponder creates a new mock instance. +func NewMockResponder(ctrl *gomock.Controller) *MockResponder { + mock := &MockResponder{ctrl: ctrl} + mock.recorder = &MockResponderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockResponder) EXPECT() *MockResponderMockRecorder { + return m.recorder +} + +// AddIP mocks base method. +func (m *MockResponder) AddIP(arg0 net.IP) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddIP", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddIP indicates an expected call of AddIP. +func (mr *MockResponderMockRecorder) AddIP(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddIP", reflect.TypeOf((*MockResponder)(nil).AddIP), arg0) +} + +// InterfaceName mocks base method. +func (m *MockResponder) InterfaceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InterfaceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// InterfaceName indicates an expected call of InterfaceName. +func (mr *MockResponderMockRecorder) InterfaceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterfaceName", reflect.TypeOf((*MockResponder)(nil).InterfaceName)) +} + +// RemoveIP mocks base method. +func (m *MockResponder) RemoveIP(arg0 net.IP) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveIP", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveIP indicates an expected call of RemoveIP. +func (mr *MockResponderMockRecorder) RemoveIP(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveIP", reflect.TypeOf((*MockResponder)(nil).RemoveIP), arg0) +} + +// Run mocks base method. +func (m *MockResponder) Run(arg0 <-chan struct{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Run", arg0) +} + +// Run indicates an expected call of Run. +func (mr *MockResponderMockRecorder) Run(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockResponder)(nil).Run), arg0) +} diff --git a/pkg/agent/util/netlink/netlink_linux.go b/pkg/agent/util/netlink/netlink_linux.go index ffe56ca02fb..01193341ea6 100644 --- a/pkg/agent/util/netlink/netlink_linux.go +++ b/pkg/agent/util/netlink/netlink_linux.go @@ -70,5 +70,9 @@ type Interface interface { LinkSetUp(link netlink.Link) error + LinkAdd(link netlink.Link) error + + LinkDel(link netlink.Link) error + ConntrackDeleteFilter(table netlink.ConntrackTableType, family netlink.InetFamily, filter netlink.CustomConntrackFilter) (uint, error) } diff --git a/pkg/agent/util/netlink/testing/mock_netlink_linux.go b/pkg/agent/util/netlink/testing/mock_netlink_linux.go index be9d25a36c2..df1ee3ec638 100644 --- a/pkg/agent/util/netlink/testing/mock_netlink_linux.go +++ b/pkg/agent/util/netlink/testing/mock_netlink_linux.go @@ -135,12 +135,26 @@ func (m *MockInterface) LinkAddAltName(arg0 netlink.Link, arg1 string) error { return ret0 } +// LinkAdd mocks base method. +func (m *MockInterface) LinkAdd(arg0 netlink.Link) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LinkAdd", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + // LinkAddAltName indicates an expected call of LinkAddAltName. func (mr *MockInterfaceMockRecorder) LinkAddAltName(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkAddAltName", reflect.TypeOf((*MockInterface)(nil).LinkAddAltName), arg0, arg1) } +// LinkAdd indicates an expected call of LinkAdd. +func (mr *MockInterfaceMockRecorder) LinkAdd(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkAdd", reflect.TypeOf((*MockInterface)(nil).LinkAdd), arg0) +} + // LinkByIndex mocks base method. func (m *MockInterface) LinkByIndex(arg0 int) (netlink.Link, error) { m.ctrl.T.Helper() @@ -178,6 +192,13 @@ func (m *MockInterface) LinkDelAltName(arg0 netlink.Link, arg1 string) error { ret0, _ := ret[0].(error) return ret0 } +// LinkDel mocks base method. +func (m *MockInterface) LinkDel(arg0 netlink.Link) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LinkDel", arg0) + ret0, _ := ret[0].(error) + return ret0 +} // LinkDelAltName indicates an expected call of LinkDelAltName. func (mr *MockInterfaceMockRecorder) LinkDelAltName(arg0, arg1 any) *gomock.Call { @@ -185,6 +206,12 @@ func (mr *MockInterfaceMockRecorder) LinkDelAltName(arg0, arg1 any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDelAltName", reflect.TypeOf((*MockInterface)(nil).LinkDelAltName), arg0, arg1) } +// LinkDel indicates an expected call of LinkDel. +func (mr *MockInterfaceMockRecorder) LinkDel(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDel", reflect.TypeOf((*MockInterface)(nil).LinkDel), arg0) +} + // LinkSetDown mocks base method. func (m *MockInterface) LinkSetDown(arg0 netlink.Link) error { m.ctrl.T.Helper()