feat: Add native linux endpoint client to prep removing OVS (#1471)
* Native Endpoint Client Add Endpoints * AddEndpointRules, ConfigureContainerInterfacesAndRoutes * Changed interface names, log statements nw.extIf.Name > eth0 (eth0) eth0.vlanid > eth0.X (eth0.1) %s%s hostIfName > vnet (A1veth0) %s%s-2 contIfName > container (B1veth0) * Renaming, using lib to set ns * Namespace "path" is /var/run/netns/<NS> * Loopback set up, Remove auto kernel subnet route * Cannot set link to up if it's in another NS * Multiple containers on same VNET NS * Delete Endpoint routes on Delete * Minimizing netns usage * Moving NS Exec Code * Further minimized netns.Set usage * Moved helper methods down, drafted tests * Removed DevName from Route Info, more tests * Test existing vnet ns, delete endpoint * NetNS interface for testing * Separated tests by namespace * Endpoints delete if they cannot be moved into NS * Namespace netns tests * Added Native Client to deleteEndpointImpl * Deletion of Endpoints Impl and Tests * Cleaned code (Tests ok) * Moved mock/netns to package (Tests ok) * Fixing Netns (wip) Moved netnsinterface to consumer package (network). Removed "Netns" from "NewNetns" and "NewMockNetns" as it is unambiguous. Changed uintptr to int and casted the int to uintptr when needed later. * Using errors.Wrap for error context (wip) * Removed sentence case (wip) * Removing variable predeclaration * Removed NewNativeEndpointClient Directly instantiating struct because nothing special happens in NewNativeEndpointClient * Removed generics from ExecuteInNS * Removed uintptr from mocknetns, tests compile Forgot to remove uintptr from mocknetns * Fix tests, lint * Fixes from linter Works on VMSS * Replacing references to ethX with vlan veth * Removed unnecessary log * Removed unnecessary mac, fix tests * Mockns method name enum * Unable to use GetNetworkInterfaceByName due to NS If I use GetNetworkInterface, I need to be in the vnet NS, but that means I will need to call ExecuteInNS, which causes tests to fail. * Fixes from linter * Assume if NS exists, vlan veth exists Tests ok * Fixes for Linter * Fix delete tests * Fix delete tests bug * Go mod tidy for linting Hopefully this fixes the windows lint error * No lint on vishvananda netns Maybe this will fix the windows linter? * Build linux only for netns package Maybe this fixes the linter error? * Remove nolint to see if linter fails * Moved netns interface to caller, generalized tests Tests ok, Native ok * Typos * Reordered if statement, unwrapped arp Tests ok, ping ok, wget ok * Renamed veth, fixed logs * Made deleteEndpoints logic clearer, renamed error * Renamed eth0 to primaryHostIfName, vlanEth to vlanIf
This commit is contained in:
Родитель
7640ebc35f
Коммит
d57e24ee25
3
go.mod
3
go.mod
|
@ -104,7 +104,8 @@ require (
|
|||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
|
|
7
go.sum
7
go.sum
|
@ -830,13 +830,14 @@ github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52
|
|||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
type Netns struct{}
|
||||
|
||||
func New() *Netns {
|
||||
return &Netns{}
|
||||
}
|
||||
|
||||
func (f *Netns) Get() (int, error) {
|
||||
nsHandle, err := netns.Get()
|
||||
return int(nsHandle), errors.Wrap(err, "netns impl")
|
||||
}
|
||||
|
||||
func (f *Netns) GetFromName(name string) (int, error) {
|
||||
nsHandle, err := netns.GetFromName(name)
|
||||
return int(nsHandle), errors.Wrap(err, "netns impl")
|
||||
}
|
||||
|
||||
func (f *Netns) Set(fileDescriptor int) error {
|
||||
return errors.Wrap(netns.Set(netns.NsHandle(fileDescriptor)), "netns impl")
|
||||
}
|
||||
|
||||
func (f *Netns) NewNamed(name string) (int, error) {
|
||||
nsHandle, err := netns.NewNamed(name)
|
||||
return int(nsHandle), errors.Wrap(err, "netns impl")
|
||||
}
|
||||
|
||||
func (f *Netns) DeleteNamed(name string) error {
|
||||
return errors.Wrap(netns.DeleteNamed(name), "netns impl")
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/Azure/azure-container-networking/log"
|
||||
"github.com/Azure/azure-container-networking/netio"
|
||||
"github.com/Azure/azure-container-networking/netlink"
|
||||
"github.com/Azure/azure-container-networking/netns"
|
||||
"github.com/Azure/azure-container-networking/network/networkutils"
|
||||
"github.com/Azure/azure-container-networking/ovsctl"
|
||||
"github.com/Azure/azure-container-networking/platform"
|
||||
|
@ -89,21 +90,42 @@ func (nw *network) newEndpointImpl(_ apipaClient, nl netlink.NetlinkInterface, p
|
|||
}
|
||||
|
||||
if vlanid != 0 {
|
||||
log.Printf("OVS client")
|
||||
if _, ok := epInfo.Data[SnatBridgeIPKey]; ok {
|
||||
nw.SnatBridgeIP = epInfo.Data[SnatBridgeIPKey].(string)
|
||||
}
|
||||
if nw.Mode == opModeNative {
|
||||
log.Printf("Native client")
|
||||
vlanVethName := fmt.Sprintf("%s.%d", nw.extIf.Name, vlanid)
|
||||
vnetNSName := fmt.Sprintf("az_ns_%d", vlanid)
|
||||
|
||||
epClient = NewOVSEndpointClient(
|
||||
nw,
|
||||
epInfo,
|
||||
hostIfName,
|
||||
contIfName,
|
||||
vlanid,
|
||||
localIP,
|
||||
nl,
|
||||
ovsctl.NewOvsctl(),
|
||||
plc)
|
||||
epClient = &NativeEndpointClient{
|
||||
primaryHostIfName: nw.extIf.Name,
|
||||
vlanIfName: vlanVethName,
|
||||
vnetVethName: hostIfName,
|
||||
containerVethName: contIfName,
|
||||
vnetNSName: vnetNSName,
|
||||
nw: nw,
|
||||
vlanID: vlanid,
|
||||
netnsClient: netns.New(),
|
||||
netlink: nl,
|
||||
netioshim: &netio.NetIO{},
|
||||
plClient: plc,
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
}
|
||||
} else {
|
||||
log.Printf("OVS client")
|
||||
if _, ok := epInfo.Data[SnatBridgeIPKey]; ok {
|
||||
nw.SnatBridgeIP = epInfo.Data[SnatBridgeIPKey].(string)
|
||||
}
|
||||
|
||||
epClient = NewOVSEndpointClient(
|
||||
nw,
|
||||
epInfo,
|
||||
hostIfName,
|
||||
contIfName,
|
||||
vlanid,
|
||||
localIP,
|
||||
nl,
|
||||
ovsctl.NewOvsctl(),
|
||||
plc)
|
||||
}
|
||||
} else if nw.Mode != opModeTransparent {
|
||||
log.Printf("Bridge client")
|
||||
epClient = NewLinuxBridgeEndpointClient(nw.extIf, hostIfName, contIfName, nw.Mode, nl, plc)
|
||||
|
@ -239,7 +261,28 @@ func (nw *network) deleteEndpointImpl(nl netlink.NetlinkInterface, plc platform.
|
|||
// entering the container netns and hence works both for CNI and CNM.
|
||||
if ep.VlanID != 0 {
|
||||
epInfo := ep.getInfo()
|
||||
epClient = NewOVSEndpointClient(nw, epInfo, ep.HostIfName, "", ep.VlanID, ep.LocalIP, nl, ovsctl.NewOvsctl(), plc)
|
||||
if nw.Mode == opModeNative {
|
||||
log.Printf("Native client")
|
||||
vlanVethName := fmt.Sprintf("%s.%d", nw.extIf.Name, ep.VlanID)
|
||||
vnetNSName := fmt.Sprintf("az_ns_%d", ep.VlanID)
|
||||
|
||||
epClient = &NativeEndpointClient{
|
||||
primaryHostIfName: nw.extIf.Name,
|
||||
vlanIfName: vlanVethName,
|
||||
vnetVethName: ep.HostIfName,
|
||||
containerVethName: "",
|
||||
vnetNSName: vnetNSName,
|
||||
nw: nw,
|
||||
vlanID: ep.VlanID,
|
||||
netnsClient: netns.New(),
|
||||
netlink: nl,
|
||||
netioshim: &netio.NetIO{},
|
||||
plClient: plc,
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
}
|
||||
} else {
|
||||
epClient = NewOVSEndpointClient(nw, epInfo, ep.HostIfName, "", ep.VlanID, ep.LocalIP, nl, ovsctl.NewOvsctl(), plc)
|
||||
}
|
||||
} else if nw.Mode != opModeTransparent {
|
||||
epClient = NewLinuxBridgeEndpointClient(nw.extIf, ep.HostIfName, "", nw.Mode, nl, plc)
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//go:build windows
|
||||
//+build windows
|
||||
// +build windows
|
||||
|
||||
package hnswrapper
|
||||
|
||||
|
|
|
@ -19,5 +19,5 @@ type HnsV1WrapperInterface interface {
|
|||
GetHNSEndpointByID(endpointID string) (*hcsshim.HNSEndpoint, error)
|
||||
HotAttachEndpoint(containerID string, endpointID string) error
|
||||
IsAttached(hnsep *hcsshim.HNSEndpoint, containerID string) (bool, error)
|
||||
GetHNSGlobals() (*hcsshim.HNSGlobals, error)
|
||||
GetHNSGlobals() (*hcsshim.HNSGlobals, error)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func NewHnsv2wrapperFake() *Hnsv2wrapperFake {
|
|||
}
|
||||
}
|
||||
|
||||
func delayHnsCall(delay time.Duration){
|
||||
func delayHnsCall(delay time.Duration) {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,407 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Azure/azure-container-networking/log"
|
||||
"github.com/Azure/azure-container-networking/netio"
|
||||
"github.com/Azure/azure-container-networking/netlink"
|
||||
"github.com/Azure/azure-container-networking/network/networkutils"
|
||||
"github.com/Azure/azure-container-networking/platform"
|
||||
"github.com/pkg/errors"
|
||||
vishnetlink "github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
azureMac = "12:34:56:78:9a:bc" // Packets leaving the VM should have this MAC
|
||||
loopbackIf = "lo" // The name of the loopback interface
|
||||
numDefaultRoutes = 2 // VNET NS, when no containers use it, has this many routes
|
||||
)
|
||||
|
||||
type netnsClient interface {
|
||||
Get() (fileDescriptor int, err error)
|
||||
GetFromName(name string) (fileDescriptor int, err error)
|
||||
Set(fileDescriptor int) (err error)
|
||||
NewNamed(name string) (fileDescriptor int, err error)
|
||||
DeleteNamed(name string) (err error)
|
||||
}
|
||||
type NativeEndpointClient struct {
|
||||
primaryHostIfName string // So like eth0
|
||||
vlanIfName string // So like eth0.1
|
||||
vnetVethName string // Peer is containerVethName
|
||||
containerVethName string // Peer is vnetVethName
|
||||
|
||||
vnetMac net.HardwareAddr
|
||||
containerMac net.HardwareAddr
|
||||
|
||||
vnetNSName string
|
||||
vnetNSFileDescriptor int
|
||||
|
||||
nw *network
|
||||
vlanID int
|
||||
netnsClient netnsClient
|
||||
netlink netlink.NetlinkInterface
|
||||
netioshim netio.NetIOInterface
|
||||
plClient platform.ExecClient
|
||||
netUtilsClient networkutils.NetworkUtils
|
||||
}
|
||||
|
||||
// Adds interfaces to the vnet (created if not existing) and vm namespace
|
||||
func (client *NativeEndpointClient) AddEndpoints(epInfo *EndpointInfo) error {
|
||||
// VM Namespace
|
||||
err := client.PopulateVM(epInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// VNET Namespace
|
||||
return ExecuteInNS(client.vnetNSName, func() error {
|
||||
return client.PopulateVnet(epInfo)
|
||||
})
|
||||
}
|
||||
|
||||
// Called from AddEndpoints, Namespace: VM
|
||||
func (client *NativeEndpointClient) PopulateVM(epInfo *EndpointInfo) error {
|
||||
vmNS, err := client.netnsClient.Get()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get vm ns handle")
|
||||
}
|
||||
|
||||
log.Printf("[native] Checking if NS exists...")
|
||||
vnetNS, existingErr := client.netnsClient.GetFromName(client.vnetNSName)
|
||||
// If the ns does not exist, the below code will trigger to create it
|
||||
// This will also (we assume) mean the vlan veth does not exist
|
||||
if existingErr != nil {
|
||||
// We assume the only possible error is that the namespace doesn't exist
|
||||
log.Printf("[native] No existing NS detected. Creating the vnet namespace and switching to it")
|
||||
vnetNS, err = client.netnsClient.NewNamed(client.vnetNSName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create vnet ns")
|
||||
}
|
||||
client.vnetNSFileDescriptor = vnetNS
|
||||
deleteNSIfNotNilErr := client.netnsClient.Set(vmNS)
|
||||
// Any failure will trigger removing the namespace created
|
||||
defer func() {
|
||||
if deleteNSIfNotNilErr != nil {
|
||||
log.Logf("[native] Removing vnet ns due to failure...")
|
||||
err = client.netnsClient.DeleteNamed(client.vnetNSName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to cleanup/delete ns after failing to create vlan veth")
|
||||
}
|
||||
}
|
||||
}()
|
||||
if deleteNSIfNotNilErr != nil {
|
||||
return errors.Wrap(deleteNSIfNotNilErr, "failed to set current ns to vm")
|
||||
}
|
||||
|
||||
// Now create vlan veth
|
||||
log.Printf("[native] Create the host vlan link after getting eth0: %s", client.primaryHostIfName)
|
||||
// Get parent interface index. Index is consistent across libraries.
|
||||
eth0, deleteNSIfNotNilErr := client.netioshim.GetNetworkInterfaceByName(client.primaryHostIfName)
|
||||
if deleteNSIfNotNilErr != nil {
|
||||
return errors.Wrap(deleteNSIfNotNilErr, "failed to get eth0 interface")
|
||||
}
|
||||
linkAttrs := vishnetlink.NewLinkAttrs()
|
||||
linkAttrs.Name = client.vlanIfName
|
||||
// Set the peer
|
||||
linkAttrs.ParentIndex = eth0.Index
|
||||
link := &vishnetlink.Vlan{
|
||||
LinkAttrs: linkAttrs,
|
||||
VlanId: client.vlanID,
|
||||
}
|
||||
log.Printf("[native] Attempting to create %s link in VM NS", client.vlanIfName)
|
||||
// Create vlan veth
|
||||
deleteNSIfNotNilErr = vishnetlink.LinkAdd(link)
|
||||
if deleteNSIfNotNilErr != nil {
|
||||
// Any failure to add the link should error (auto delete NS)
|
||||
return errors.Wrap(deleteNSIfNotNilErr, "failed to create vlan vnet link after making new ns")
|
||||
}
|
||||
// vlan veth was created successfully, so move the vlan veth you created
|
||||
log.Printf("[native] Move vlan link (%s) to vnet NS: %d", client.vlanIfName, uintptr(client.vnetNSFileDescriptor))
|
||||
deleteNSIfNotNilErr = client.netlink.SetLinkNetNs(client.vlanIfName, uintptr(client.vnetNSFileDescriptor))
|
||||
if deleteNSIfNotNilErr != nil {
|
||||
if delErr := client.netlink.DeleteLink(client.vlanIfName); delErr != nil {
|
||||
log.Errorf("deleting vlan veth failed on addendpoint failure")
|
||||
}
|
||||
return errors.Wrap(deleteNSIfNotNilErr, "deleting vlan veth in vm ns due to addendpoint failure")
|
||||
}
|
||||
} else {
|
||||
log.Printf("[native] Existing NS (%s) detected. Assuming %s exists too", client.vnetNSName, client.vlanIfName)
|
||||
}
|
||||
client.vnetNSFileDescriptor = vnetNS
|
||||
|
||||
if err = client.netUtilsClient.CreateEndpoint(client.vnetVethName, client.containerVethName); err != nil {
|
||||
return errors.Wrap(err, "failed to create veth pair")
|
||||
}
|
||||
|
||||
if err = client.netlink.SetLinkNetNs(client.vnetVethName, uintptr(client.vnetNSFileDescriptor)); err != nil {
|
||||
if delErr := client.netlink.DeleteLink(client.vnetVethName); delErr != nil {
|
||||
log.Errorf("Deleting vnet veth failed on addendpoint failure:%v", delErr)
|
||||
}
|
||||
return errors.Wrap(err, "failed to move vnetVethName into vnet ns, deleting")
|
||||
}
|
||||
|
||||
containerIf, err := client.netioshim.GetNetworkInterfaceByName(client.containerVethName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "container veth does not exist")
|
||||
}
|
||||
client.containerMac = containerIf.HardwareAddr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Called from AddEndpoints, Namespace: Vnet
|
||||
func (client *NativeEndpointClient) PopulateVnet(epInfo *EndpointInfo) error {
|
||||
_, err := client.netioshim.GetNetworkInterfaceByName(client.vlanIfName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "vlan veth doesn't exist")
|
||||
}
|
||||
vnetVethIf, err := client.netioshim.GetNetworkInterfaceByName(client.vnetVethName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "vnet veth doesn't exist")
|
||||
}
|
||||
client.vnetMac = vnetVethIf.HardwareAddr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *NativeEndpointClient) AddEndpointRules(epInfo *EndpointInfo) error {
|
||||
// There are no rules to add here
|
||||
// Described as rules on ip addresses on the container interface
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *NativeEndpointClient) DeleteEndpointRules(ep *endpoint) {
|
||||
// Never added any endpoint rules
|
||||
}
|
||||
|
||||
func (client *NativeEndpointClient) MoveEndpointsToContainerNS(epInfo *EndpointInfo, nsID uintptr) error {
|
||||
if err := client.netlink.SetLinkNetNs(client.containerVethName, nsID); err != nil {
|
||||
return errors.Wrap(err, "failed to move endpoint to container ns")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *NativeEndpointClient) SetupContainerInterfaces(epInfo *EndpointInfo) error {
|
||||
if err := client.netUtilsClient.SetupContainerInterface(client.containerVethName, epInfo.IfName); err != nil {
|
||||
return errors.Wrap(err, "failed to setup container interface")
|
||||
}
|
||||
client.containerVethName = epInfo.IfName
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Adds routes, arp entries, etc. to the vnet and container namespaces
|
||||
func (client *NativeEndpointClient) ConfigureContainerInterfacesAndRoutes(epInfo *EndpointInfo) error {
|
||||
// Container NS
|
||||
err := client.ConfigureContainerInterfacesAndRoutesImpl(epInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Switch to vnet NS and call ConfigureVnetInterfacesAndRoutes
|
||||
return ExecuteInNS(client.vnetNSName, func() error {
|
||||
return client.ConfigureVnetInterfacesAndRoutesImpl(epInfo)
|
||||
})
|
||||
}
|
||||
|
||||
// Called from ConfigureContainerInterfacesAndRoutes, Namespace: Container
|
||||
func (client *NativeEndpointClient) ConfigureContainerInterfacesAndRoutesImpl(epInfo *EndpointInfo) error {
|
||||
if err := client.netUtilsClient.AssignIPToInterface(client.containerVethName, epInfo.IPAddresses); err != nil {
|
||||
return errors.Wrap(err, "failed to assign ips to container veth interface")
|
||||
}
|
||||
// kernel subnet route auto added by above call must be removed
|
||||
for _, ipAddr := range epInfo.IPAddresses {
|
||||
_, ipnet, _ := net.ParseCIDR(ipAddr.String())
|
||||
routeInfo := RouteInfo{
|
||||
Dst: *ipnet,
|
||||
Scope: netlink.RT_SCOPE_LINK,
|
||||
Protocol: netlink.RTPROT_KERNEL,
|
||||
}
|
||||
if err := deleteRoutes(client.netlink, client.netioshim, client.containerVethName, []RouteInfo{routeInfo}); err != nil {
|
||||
return errors.Wrap(err, "failed to remove kernel subnet route")
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.AddDefaultRoutes(client.containerVethName); err != nil {
|
||||
return errors.Wrap(err, "failed container ns add default routes")
|
||||
}
|
||||
if err := client.AddDefaultArp(client.containerVethName, client.vnetMac.String()); err != nil {
|
||||
return errors.Wrap(err, "failed container ns add default arp")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Called from ConfigureContainerInterfacesAndRoutes, Namespace: Vnet
|
||||
func (client *NativeEndpointClient) ConfigureVnetInterfacesAndRoutesImpl(epInfo *EndpointInfo) error {
|
||||
err := client.netlink.SetLinkState(loopbackIf, true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to set loopback link state to up")
|
||||
}
|
||||
|
||||
// Add route specifying which device the pod ip(s) are on
|
||||
routeInfoList := client.GetVnetRoutes(epInfo.IPAddresses)
|
||||
|
||||
if err = client.AddDefaultRoutes(client.vlanIfName); err != nil {
|
||||
return errors.Wrap(err, "failed vnet ns add default/gateway routes (idempotent)")
|
||||
}
|
||||
if err = client.AddDefaultArp(client.vlanIfName, azureMac); err != nil {
|
||||
return errors.Wrap(err, "failed vnet ns add default arp entry (idempotent)")
|
||||
}
|
||||
if err = addRoutes(client.netlink, client.netioshim, client.vnetVethName, routeInfoList); err != nil {
|
||||
return errors.Wrap(err, "failed adding routes to vnet specific to this container")
|
||||
}
|
||||
// Return to ConfigureContainerInterfacesAndRoutes
|
||||
return err
|
||||
}
|
||||
|
||||
// Helper that gets the routes in the vnet NS for a particular list of IP addresses
|
||||
// Example: 192.168.0.4 dev <device which connects to NS with that IP> proto static
|
||||
func (client *NativeEndpointClient) GetVnetRoutes(ipAddresses []net.IPNet) []RouteInfo {
|
||||
routeInfoList := make([]RouteInfo, 0, len(ipAddresses))
|
||||
// Add route specifying which device the pod ip(s) are on
|
||||
for _, ipAddr := range ipAddresses {
|
||||
var (
|
||||
routeInfo RouteInfo
|
||||
ipNet net.IPNet
|
||||
)
|
||||
|
||||
if ipAddr.IP.To4() != nil {
|
||||
ipNet = net.IPNet{IP: ipAddr.IP, Mask: net.CIDRMask(ipv4FullMask, ipv4Bits)}
|
||||
} else {
|
||||
ipNet = net.IPNet{IP: ipAddr.IP, Mask: net.CIDRMask(ipv6FullMask, ipv6Bits)}
|
||||
}
|
||||
log.Printf("[net] Native client adding route for the ip %v", ipNet.String())
|
||||
routeInfo.Dst = ipNet
|
||||
routeInfoList = append(routeInfoList, routeInfo)
|
||||
|
||||
}
|
||||
return routeInfoList
|
||||
}
|
||||
|
||||
// Helper that creates routing rules for the current NS which direct packets
|
||||
// to the virtual gateway ip on linkToName device interface
|
||||
// Route 1: 169.254.1.1 dev <linkToName>
|
||||
// Route 2: default via 169.254.1.1 dev <linkToName>
|
||||
func (client *NativeEndpointClient) AddDefaultRoutes(linkToName string) error {
|
||||
// Add route for virtualgwip (ip route add 169.254.1.1/32 dev eth0)
|
||||
virtualGwIP, virtualGwNet, _ := net.ParseCIDR(virtualGwIPString)
|
||||
routeInfo := RouteInfo{
|
||||
Dst: *virtualGwNet,
|
||||
Scope: netlink.RT_SCOPE_LINK,
|
||||
}
|
||||
// Difference between interface name in addRoutes and DevName: in RouteInfo?
|
||||
if err := addRoutes(client.netlink, client.netioshim, linkToName, []RouteInfo{routeInfo}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add default route (ip route add default via 169.254.1.1 dev eth0)
|
||||
_, defaultIPNet, _ := net.ParseCIDR(defaultGwCidr)
|
||||
dstIP := net.IPNet{IP: net.ParseIP(defaultGw), Mask: defaultIPNet.Mask}
|
||||
routeInfo = RouteInfo{
|
||||
Dst: dstIP,
|
||||
Gw: virtualGwIP,
|
||||
}
|
||||
|
||||
if err := addRoutes(client.netlink, client.netioshim, linkToName, []RouteInfo{routeInfo}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper that creates arp entry for the current NS which maps the virtual
|
||||
// gateway (169.254.1.1) to destMac on a particular interfaceName
|
||||
// Example: (169.254.1.1) at 12:34:56:78:9a:bc [ether] PERM on <interfaceName>
|
||||
func (client *NativeEndpointClient) AddDefaultArp(interfaceName, destMac string) error {
|
||||
_, virtualGwNet, _ := net.ParseCIDR(virtualGwIPString)
|
||||
log.Printf("[net] Adding static arp for IP address %v and MAC %v in namespace",
|
||||
virtualGwNet.String(), destMac)
|
||||
hardwareAddr, err := net.ParseMAC(destMac)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse mac")
|
||||
}
|
||||
if err := client.netlink.AddOrRemoveStaticArp(netlink.ADD, interfaceName, virtualGwNet.IP, hardwareAddr, false); err != nil {
|
||||
return fmt.Errorf("adding arp entry failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *NativeEndpointClient) DeleteEndpoints(ep *endpoint) error {
|
||||
return ExecuteInNS(client.vnetNSName, func() error {
|
||||
// Passing in functionality to get number of routes after deletion
|
||||
getNumRoutesLeft := func() (int, error) {
|
||||
routes, err := vishnetlink.RouteList(nil, vishnetlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to get num routes left")
|
||||
}
|
||||
return len(routes), nil
|
||||
}
|
||||
|
||||
return client.DeleteEndpointsImpl(ep, getNumRoutesLeft)
|
||||
})
|
||||
}
|
||||
|
||||
// getNumRoutesLeft is a function which gets the current number of routes in the namespace. Namespace: Vnet
|
||||
func (client *NativeEndpointClient) DeleteEndpointsImpl(ep *endpoint, getNumRoutesLeft func() (int, error)) error {
|
||||
routeInfoList := client.GetVnetRoutes(ep.IPAddresses)
|
||||
if err := deleteRoutes(client.netlink, client.netioshim, client.vnetVethName, routeInfoList); err != nil {
|
||||
return errors.Wrap(err, "failed to remove routes")
|
||||
}
|
||||
|
||||
routesLeft, err := getNumRoutesLeft()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[native] There are %d routes remaining after deletion", routesLeft)
|
||||
|
||||
if routesLeft <= numDefaultRoutes {
|
||||
// Deletes default arp, default routes, vlan veth; there are two default routes
|
||||
// so when we have <= numDefaultRoutes routes left, no containers use this namespace
|
||||
log.Printf("[native] Deleting namespace %s as no containers occupy it", client.vnetNSName)
|
||||
delErr := client.netnsClient.DeleteNamed(client.vnetNSName)
|
||||
if delErr != nil {
|
||||
return errors.Wrap(delErr, "failed to delete namespace")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function that allows executing a function in a VM namespace
|
||||
// Does not work for process namespaces
|
||||
func ExecuteInNS(nsName string, f func() error) error {
|
||||
// Current namespace
|
||||
returnedTo, err := GetCurrentThreadNamespace()
|
||||
if err != nil {
|
||||
log.Errorf("[ExecuteInNS] Could not get NS we are in: %v", err)
|
||||
} else {
|
||||
log.Printf("[ExecuteInNS] In NS before switch: %s", returnedTo.file.Name())
|
||||
}
|
||||
|
||||
// Open the network namespace
|
||||
log.Printf("[ExecuteInNS] Opening ns %v.", fmt.Sprintf("/var/run/netns/%s", nsName))
|
||||
ns, err := OpenNamespace(fmt.Sprintf("/var/run/netns/%s", nsName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ns.Close()
|
||||
// Enter the network namespace
|
||||
log.Printf("[ExecuteInNS] Entering ns %s.", ns.file.Name())
|
||||
if err := ns.Enter(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Exit network namespace
|
||||
defer func() {
|
||||
log.Printf("[ExecuteInNS] Exiting ns %s.", ns.file.Name())
|
||||
if err := ns.Exit(); err != nil {
|
||||
log.Errorf("[ExecuteInNS] Could not exit ns, err:%v.", err)
|
||||
}
|
||||
returnedTo, err := GetCurrentThreadNamespace()
|
||||
if err != nil {
|
||||
log.Errorf("[ExecuteInNS] Could not get NS we returned to: %v", err)
|
||||
} else {
|
||||
log.Printf("[ExecuteInNS] Returned to NS: %s", returnedTo.file.Name())
|
||||
}
|
||||
}()
|
||||
return f()
|
||||
}
|
|
@ -0,0 +1,665 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-container-networking/netio"
|
||||
"github.com/Azure/azure-container-networking/netlink"
|
||||
"github.com/Azure/azure-container-networking/network/networkutils"
|
||||
"github.com/Azure/azure-container-networking/platform"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var errNetnsMock = errors.New("mock netns error")
|
||||
|
||||
func newNetnsErrorMock(errStr string) error {
|
||||
return errors.Wrap(errNetnsMock, errStr)
|
||||
}
|
||||
|
||||
type mockNetns struct {
|
||||
get func() (fileDescriptor int, err error)
|
||||
getFromName func(name string) (fileDescriptor int, err error)
|
||||
set func(fileDescriptor int) (err error)
|
||||
newNamed func(name string) (fileDescriptor int, err error)
|
||||
deleteNamed func(name string) (err error)
|
||||
}
|
||||
|
||||
func (netns *mockNetns) Get() (fileDescriptor int, err error) {
|
||||
return netns.get()
|
||||
}
|
||||
|
||||
func (netns *mockNetns) GetFromName(name string) (fileDescriptor int, err error) {
|
||||
return netns.getFromName(name)
|
||||
}
|
||||
|
||||
func (netns *mockNetns) Set(fileDescriptor int) (err error) {
|
||||
return netns.set(fileDescriptor)
|
||||
}
|
||||
|
||||
func (netns *mockNetns) NewNamed(name string) (fileDescriptor int, err error) {
|
||||
return netns.newNamed(name)
|
||||
}
|
||||
|
||||
func (netns *mockNetns) DeleteNamed(name string) (err error) {
|
||||
return netns.deleteNamed(name)
|
||||
}
|
||||
|
||||
func defaultGet() (int, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func defaultGetFromName(name string) (int, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func defaultSet(handle int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultNewNamed(name string) (int, error) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func defaultDeleteNamed(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNativeAddEndpoints(t *testing.T) {
|
||||
nl := netlink.NewMockNetlink(false, "")
|
||||
plc := platform.NewMockExecClient(false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
client *NativeEndpointClient
|
||||
epInfo *EndpointInfo
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
// Populating VM with data and creating interfaces/links
|
||||
{
|
||||
name: "Add endpoints create vnet ns failure",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: defaultGet,
|
||||
getFromName: func(name string) (fileDescriptor int, err error) {
|
||||
return 0, newNetnsErrorMock("netns failure")
|
||||
},
|
||||
newNamed: func(name string) (fileDescriptor int, err error) {
|
||||
return 0, newNetnsErrorMock("netns failure")
|
||||
},
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed to create vnet ns: netns failure: " + errNetnsMock.Error(),
|
||||
},
|
||||
{
|
||||
name: "Add endpoints with existing vnet ns",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: defaultGet,
|
||||
getFromName: defaultGetFromName,
|
||||
newNamed: defaultNewNamed,
|
||||
set: defaultSet,
|
||||
deleteNamed: defaultDeleteNamed,
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Add endpoints netlink fail",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: defaultGet,
|
||||
getFromName: defaultGetFromName,
|
||||
newNamed: defaultNewNamed,
|
||||
set: defaultSet,
|
||||
deleteNamed: defaultDeleteNamed,
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(true, "netlink fail"),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed to move vnetVethName into vnet ns, deleting: " + netlink.ErrorMockNetlink.Error() + " : netlink fail",
|
||||
},
|
||||
{
|
||||
name: "Add endpoints get interface fail for primary interface (eth0)",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: defaultGet,
|
||||
getFromName: func(name string) (fileDescriptor int, err error) {
|
||||
return 0, newNetnsErrorMock("netns failure")
|
||||
},
|
||||
newNamed: defaultNewNamed,
|
||||
set: defaultSet,
|
||||
deleteNamed: defaultDeleteNamed,
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(true, 1),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed to get eth0 interface: " + netio.ErrMockNetIOFail.Error() + ":eth0",
|
||||
},
|
||||
{
|
||||
name: "Add endpoints get interface fail for getting container veth",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: defaultGet,
|
||||
getFromName: defaultGetFromName,
|
||||
newNamed: defaultNewNamed,
|
||||
set: defaultSet,
|
||||
deleteNamed: defaultDeleteNamed,
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(true, 1),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "container veth does not exist: " + netio.ErrMockNetIOFail.Error() + ":B1veth0",
|
||||
},
|
||||
{
|
||||
name: "Add endpoints NetNS Get fail",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: func() (fileDescriptor int, err error) {
|
||||
return 0, newNetnsErrorMock("netns failure")
|
||||
},
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed to get vm ns handle: netns failure: " + errNetnsMock.Error(),
|
||||
},
|
||||
{
|
||||
name: "Add endpoints NetNS Set fail",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
get: defaultGet,
|
||||
getFromName: func(name string) (fileDescriptor int, err error) {
|
||||
return 0, newNetnsErrorMock("do not fail on this error")
|
||||
},
|
||||
newNamed: defaultNewNamed,
|
||||
set: func(fileDescriptor int) (err error) {
|
||||
return newNetnsErrorMock("netns failure")
|
||||
},
|
||||
deleteNamed: defaultDeleteNamed,
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed to set current ns to vm: netns failure: " + errNetnsMock.Error(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.client.PopulateVM(tt.epInfo)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
tests = []struct {
|
||||
name string
|
||||
client *NativeEndpointClient
|
||||
epInfo *EndpointInfo
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
// Populate the client with information from the vnet and set up vnet
|
||||
{
|
||||
name: "Add endpoints get vnet veth mac address",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Add endpoints fail check vlan veth exists",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(true, 1),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "vlan veth doesn't exist: " + netio.ErrMockNetIOFail.Error() + ":eth0.1",
|
||||
},
|
||||
{
|
||||
name: "Add endpoints fail check vnet veth exists",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(true, 2),
|
||||
},
|
||||
epInfo: &EndpointInfo{},
|
||||
wantErr: true,
|
||||
wantErrMsg: "vnet veth doesn't exist: " + netio.ErrMockNetIOFail.Error() + ":A1veth0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.client.PopulateVnet(tt.epInfo)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeDeleteEndpoints(t *testing.T) {
|
||||
nl := netlink.NewMockNetlink(false, "")
|
||||
plc := platform.NewMockExecClient(false)
|
||||
IPAddresses := []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.6"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
client *NativeEndpointClient
|
||||
ep *endpoint
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
routesLeft func() (int, error)
|
||||
}{
|
||||
{
|
||||
name: "Delete endpoint delete vnet ns",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
deleteNamed: defaultDeleteNamed,
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
ep: &endpoint{
|
||||
IPAddresses: IPAddresses,
|
||||
},
|
||||
routesLeft: func() (int, error) {
|
||||
return numDefaultRoutes, nil
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Delete endpoint do not delete vnet ns it is still in use",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
deleteNamed: func(name string) (err error) {
|
||||
return newNetnsErrorMock("netns failure")
|
||||
},
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
ep: &endpoint{
|
||||
IPAddresses: IPAddresses,
|
||||
},
|
||||
routesLeft: func() (int, error) {
|
||||
return numDefaultRoutes + 1, nil
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Delete endpoint fail to delete namespace",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
netnsClient: &mockNetns{
|
||||
deleteNamed: func(name string) (err error) {
|
||||
return newNetnsErrorMock("netns failure")
|
||||
},
|
||||
},
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
ep: &endpoint{
|
||||
IPAddresses: IPAddresses,
|
||||
},
|
||||
routesLeft: func() (int, error) {
|
||||
return numDefaultRoutes, nil
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed to delete namespace: netns failure: " + errNetnsMock.Error(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.client.DeleteEndpointsImpl(tt.ep, tt.routesLeft)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNativeConfigureContainerInterfacesAndRoutes(t *testing.T) {
|
||||
nl := netlink.NewMockNetlink(false, "")
|
||||
plc := platform.NewMockExecClient(false)
|
||||
|
||||
vnetMac, _ := net.ParseMAC("ab:cd:ef:12:34:56")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
client *NativeEndpointClient
|
||||
epInfo *EndpointInfo
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "Configure interface and routes good path for container",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
vnetMac: vnetMac,
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{
|
||||
IPAddresses: []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Configure interface and routes multiple IPs",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
vnetMac: vnetMac,
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{
|
||||
IPAddresses: []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.6"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.8"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Configure interface and routes assign ip fail",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
vnetMac: vnetMac,
|
||||
netlink: netlink.NewMockNetlink(true, "netlink fail"),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{
|
||||
IPAddresses: []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "netlink fail",
|
||||
},
|
||||
{
|
||||
name: "Configure interface and routes container 2nd default route added fail",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
vnetMac: vnetMac,
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(true, 3),
|
||||
},
|
||||
epInfo: &EndpointInfo{
|
||||
IPAddresses: []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed container ns add default routes: addRoutes failed: " + netio.ErrMockNetIOFail.Error() + ":B1veth0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.client.ConfigureContainerInterfacesAndRoutesImpl(tt.epInfo)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
tests = []struct {
|
||||
name string
|
||||
client *NativeEndpointClient
|
||||
epInfo *EndpointInfo
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "Configure interface and routes good path for vnet",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
vnetMac: vnetMac,
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(false, 0),
|
||||
},
|
||||
epInfo: &EndpointInfo{
|
||||
IPAddresses: []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
// fail route that tells which device container ip is on for vnet
|
||||
name: "Configure interface and routes fail final routes for vnet",
|
||||
client: &NativeEndpointClient{
|
||||
primaryHostIfName: "eth0",
|
||||
vlanIfName: "eth0.1",
|
||||
vnetVethName: "A1veth0",
|
||||
containerVethName: "B1veth0",
|
||||
vnetNSName: "az_ns_1",
|
||||
vnetMac: vnetMac,
|
||||
netlink: netlink.NewMockNetlink(false, ""),
|
||||
plClient: platform.NewMockExecClient(false),
|
||||
netUtilsClient: networkutils.NewNetworkUtils(nl, plc),
|
||||
netioshim: netio.NewMockNetIO(true, 3),
|
||||
},
|
||||
epInfo: &EndpointInfo{
|
||||
IPAddresses: []net.IPNet{
|
||||
{
|
||||
IP: net.ParseIP("192.168.0.4"),
|
||||
Mask: net.CIDRMask(subnetv4Mask, ipv4Bits),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "failed adding routes to vnet specific to this container: addRoutes failed: " + netio.ErrMockNetIOFail.Error() + ":A1veth0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.client.ConfigureVnetInterfacesAndRoutesImpl(tt.epInfo)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ const (
|
|||
opModeBridge = "bridge"
|
||||
opModeTunnel = "tunnel"
|
||||
opModeTransparent = "transparent"
|
||||
opModeNative = "native"
|
||||
opModeDefault = opModeTunnel
|
||||
)
|
||||
|
||||
|
|
|
@ -88,6 +88,9 @@ func (nm *networkManager) newNetworkImpl(nwInfo *NetworkInfo, extIf *externalInt
|
|||
return nil, fmt.Errorf("Ipv6 forwarding failed: %w", err)
|
||||
}
|
||||
}
|
||||
case opModeNative:
|
||||
log.Printf("Native mode")
|
||||
ifName = extIf.Name
|
||||
default:
|
||||
return nil, errNetworkModeInvalid
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче