Skip to content

Commit

Permalink
hotunplug, unpluggedPodNic: implement reverse phase1
Browse files Browse the repository at this point in the history
The commit includes removal of bridge, tap and dummy nic if they exist.
Follow up commits will take care of the caches files deletion.

Notice:
1. The cleanup is general and not binding specific. Any link created
by whatecer binding should be removed (although we curretnly support only
bridge binding hotunplug).
2. Error removing one of the links doesn't block removal on the others.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
  • Loading branch information
AlonaKaplan committed May 23, 2023
1 parent 2282f81 commit 946e040
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 3 deletions.
4 changes: 4 additions & 0 deletions pkg/network/driver/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type NetworkHandler interface {
LinkSetUp(link netlink.Link) error
LinkSetName(link netlink.Link, name string) error
LinkAdd(link netlink.Link) error
LinkDel(link netlink.Link) error
LinkSetLearningOff(link netlink.Link) error
ParseAddr(s string) (*netlink.Addr, error)
LinkSetHardwareAddr(link netlink.Link, hwaddr net.HardwareAddr) error
Expand Down Expand Up @@ -127,6 +128,9 @@ func (h *NetworkUtilsHandler) LinkSetName(link netlink.Link, name string) error
func (h *NetworkUtilsHandler) LinkAdd(link netlink.Link) error {
return netlink.LinkAdd(link)
}
func (h *NetworkUtilsHandler) LinkDel(link netlink.Link) error {
return netlink.LinkDel(link)
}
func (h *NetworkUtilsHandler) LinkSetLearningOff(link netlink.Link) error {
return netlink.LinkSetLearning(link, false)
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/network/driver/generated_mock_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ func (_mr *_MockNetworkHandlerRecorder) LinkAdd(arg0 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LinkAdd", arg0)
}

func (_m *MockNetworkHandler) LinkDel(link netlink.Link) error {
ret := _m.ctrl.Call(_m, "LinkDel", link)
ret0, _ := ret[0].(error)
return ret0
}

func (_mr *_MockNetworkHandlerRecorder) LinkDel(arg0 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LinkDel", arg0)
}

func (_m *MockNetworkHandler) LinkSetLearningOff(link netlink.Link) error {
ret := _m.ctrl.Call(_m, "LinkSetLearningOff", link)
ret0, _ := ret[0].(error)
Expand Down
3 changes: 3 additions & 0 deletions pkg/network/setup/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ go_library(
"netstat.go",
"network.go",
"podnic.go",
"unpluggedpodnic.go",
],
importpath = "kubevirt.io/kubevirt/pkg/network/setup",
visibility = ["//visibility:public"],
Expand All @@ -29,6 +30,7 @@ go_library(
"//staging/src/kubevirt.io/client-go/log:go_default_library",
"//staging/src/kubevirt.io/client-go/precond:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/vishvananda/netlink:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
],
Expand All @@ -44,6 +46,7 @@ go_test(
"network_suite_test.go",
"network_test.go",
"podnic_test.go",
"unpluggedpodnic_test.go",
],
embed = [":go_default_library"],
deps = [
Expand Down
5 changes: 2 additions & 3 deletions pkg/network/setup/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,8 @@ func (n *VMNetworkConfigurator) UnplugPodNetworksPhase1(vmi *v1.VirtualMachineIn
err := configState.UnplugNetworks(
vmi,
func(network string) error {
// TODO clean cache
// TODO remove bridge and tap device
return nil
unpluggedPodNic := NewUnpluggedpodnic(network, n.handler)
return unpluggedPodNic.UnplugPhase1()
})
if err != nil {
return fmt.Errorf("failed unplug pod networks phase1: %w", err)
Expand Down
73 changes: 73 additions & 0 deletions pkg/network/setup/unpluggedpodnic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package network

import (
"errors"

v1 "kubevirt.io/api/core/v1"

"kubevirt.io/kubevirt/pkg/network/namescheme"

"github.com/vishvananda/netlink"
k8serrors "k8s.io/apimachinery/pkg/util/errors"

netdriver "kubevirt.io/kubevirt/pkg/network/driver"
virtnetlink "kubevirt.io/kubevirt/pkg/network/link"
)

type Unpluggedpodnic struct {
networkName string
handler netdriver.NetworkHandler
}

func NewUnpluggedpodnic(network string, handler netdriver.NetworkHandler) Unpluggedpodnic {
return Unpluggedpodnic{networkName: network, handler: handler}
}

func (c Unpluggedpodnic) UnplugPhase1() error {
var unplugErrors []error

dummyNet := v1.Network{
Name: c.networkName,
NetworkSource: v1.NetworkSource{
Multus: &v1.MultusNetwork{},
},
} // TODO - remove this dummy network once we will have the absent network object
podInterfaceName := namescheme.HashedPodInterfaceName(dummyNet)
bridgeName := virtnetlink.GenerateBridgeName(podInterfaceName)
err := c.delLinkIfExists(bridgeName)
if err != nil {
unplugErrors = append(unplugErrors, err)
}

// remove extra nic
dummyIfaceName := virtnetlink.GenerateNewBridgedVmiInterfaceName(podInterfaceName)
err = c.delLinkIfExists(dummyIfaceName)
if err != nil {
unplugErrors = append(unplugErrors, err)
}

// remove tap if exists
tapDeviceName := virtnetlink.GenerateTapDeviceName(podInterfaceName)
err = c.delLinkIfExists(tapDeviceName)
if err != nil {
unplugErrors = append(unplugErrors, err)
}

// clean caches
// TODO remove all three cache files

return k8serrors.NewAggregate(unplugErrors)

}

func (c Unpluggedpodnic) delLinkIfExists(linkName string) error {
link, err := c.handler.LinkByName(linkName)
if err != nil {
var linkNotFoundErr netlink.LinkNotFoundError
if errors.As(err, &linkNotFoundErr) {
return nil
}
return err
}
return c.handler.LinkDel(link)
}
99 changes: 99 additions & 0 deletions pkg/network/setup/unpluggedpodnic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* This file is part of the KubeVirt project
*
* 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.
*
* Copyright 2023 Red Hat, Inc.
*
*/

package network_test

import (
"fmt"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/golang/mock/gomock"

"github.com/vishvananda/netlink"

netdriver "kubevirt.io/kubevirt/pkg/network/driver"
network "kubevirt.io/kubevirt/pkg/network/setup"
)

var _ = Describe("Unpluggedpodnic", func() {
var (
mockHandler *netdriver.MockNetworkHandler
ctrl *gomock.Controller
unpluggedpodnic network.Unpluggedpodnic
tapLink *netlink.GenericLink
dummyIfaceLink *netlink.GenericLink
bridgeLink *netlink.GenericLink
)
const (
tapName = "tap16477688c0e"
dummyIfaceName = "16477688c0e-nic"
bridgeName = "k6t-16477688c0e"
networkName = "blue"
)

BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
mockHandler = netdriver.NewMockNetworkHandler(ctrl)

unpluggedpodnic = network.NewUnpluggedpodnic(networkName, mockHandler)

tapLink = &netlink.GenericLink{}
dummyIfaceLink = &netlink.GenericLink{}
bridgeLink = &netlink.GenericLink{}
})

Context("UnplugPhase1", func() {
It("successfully when bridge, tap and dummy nic exist", func() {
mockHandler.EXPECT().LinkByName(tapName).Return(tapLink, nil)
mockHandler.EXPECT().LinkByName(dummyIfaceName).Return(dummyIfaceLink, nil)
mockHandler.EXPECT().LinkByName(bridgeName).Return(bridgeLink, nil)
mockHandler.EXPECT().LinkDel(tapLink).Return(nil)
mockHandler.EXPECT().LinkDel(dummyIfaceLink).Return(nil)
mockHandler.EXPECT().LinkDel(bridgeLink).Return(nil)

Expect(unpluggedpodnic.UnplugPhase1()).To(Succeed())
})
It("successfully when dummy nic doesn't exist", func() {
mockHandler.EXPECT().LinkByName(tapName).Return(tapLink, nil)
mockHandler.EXPECT().LinkByName(dummyIfaceName).Return(nil, netlink.LinkNotFoundError{})
mockHandler.EXPECT().LinkByName(bridgeName).Return(bridgeLink, nil)
mockHandler.EXPECT().LinkDel(tapLink).Return(nil)
mockHandler.EXPECT().LinkDel(bridgeLink).Return(nil)

Expect(unpluggedpodnic.UnplugPhase1()).To(Succeed())
})
It("partially fails when some of the devices fail to be removed", func() {
const (
linkByNameErr1 = "link by name error1"
linkByNameErr2 = "link by name error2"
)

mockHandler.EXPECT().LinkByName(tapName).Return(tapLink, nil)
mockHandler.EXPECT().LinkByName(dummyIfaceName).Return(nil, fmt.Errorf(linkByNameErr1))
mockHandler.EXPECT().LinkByName(bridgeName).Return(bridgeLink, fmt.Errorf(linkByNameErr2))
mockHandler.EXPECT().LinkDel(tapLink).Return(nil)

err := unpluggedpodnic.UnplugPhase1()
Expect(err.Error()).To(ContainSubstring(linkByNameErr1))
Expect(err.Error()).To(ContainSubstring(linkByNameErr1))
})
})
})

0 comments on commit 946e040

Please sign in to comment.