Projects
Eulaceura:Factory
syscontainer-tools
_service:obs_scm:0012-support-Vnic-specifying-n...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:0012-support-Vnic-specifying-network-namespace.patch of Package syscontainer-tools
From c779e4b97de5eed63184c8183b39a7d1727105dd Mon Sep 17 00:00:00 2001 From: wujichao <wujichao1@huawei.com> Date: Thu, 29 Aug 2024 12:00:46 +0800 Subject: [PATCH] support Vnic specifying network namespace --- integration_cover.sh | 94 +++++++ libnetwork/drivers/common/driver.go | 31 ++- libnetwork/drivers/common/driver_test.go | 21 +- libnetwork/drivers/driver.go | 8 + libnetwork/drivers/veth/driver.go | 191 +++++++------- libnetwork/drivers/veth/driver_test.go | 245 ++++++++++++++++++ libnetwork/interfaces.go | 10 +- libnetwork/nsutils/ns_utils.go | 33 +++ libnetwork/nsutils/ns_utils_test.go | 121 +++++++++ network.go | 23 +- test/netns_test_cover.sh | 302 +++++++++++++++++++++++ types/network.go | 56 ++++- types/network_test.go | 262 ++++++++++++++++++++ 13 files changed, 1272 insertions(+), 125 deletions(-) create mode 100644 integration_cover.sh create mode 100644 libnetwork/drivers/veth/driver_test.go create mode 100644 libnetwork/nsutils/ns_utils_test.go create mode 100644 test/netns_test_cover.sh diff --git a/integration_cover.sh b/integration_cover.sh new file mode 100644 index 0000000..ae1af79 --- /dev/null +++ b/integration_cover.sh @@ -0,0 +1,94 @@ +### + # Copyright (c) Huawei Technologies Co., Ltd. 2021-2024. All rights reserved. + # syscontainer-tools licensed under the Mulan PSL v2. + # You can use this software according to the terms and conditions of the Mulan PSL v2. + # You may obtain a copy of Mulan PSL v2 at: + # http://license.coscl.org.cn/MulanPSL2 + # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + # PURPOSE. + # See the Mulan PSL v2 for more details. + # Author: syscontainer-tools Team + # Date: 2024-07-25 + # Description: This file is used for coverage +### + +# 获取当前git仓库的路径 +project_root=$(git rev-parse --show-toplevel 2>/dev/null) +if [ -z "$git_dir" ]; then + project_root=$(cd "$(dirname "$0")" && pwd) +fi +echo "project directory is ${project_root}" + +## 项目所使用的目录 +cover_pkg=${project_root}/coverage/ +build_pkg=${project_root}/build/ +test_pkg=${project_root}/test/ +log_pkg=${project_root}/log/ + +## 集成测试覆盖率数据目录和文件 +integration_coveragePkg=${cover_pkg}/integration +integration_coverage_file=${integration_coveragePkg}/cover_integration_test_all.out +integration_coverage_html=${integration_coveragePkg}/cover_integration_test_all.html +integration_coverage_func=${integration_coveragePkg}/cover_integration_test_all_func_cover +integration_coverage_pkg=${integration_coveragePkg}/cover_integration_test_all_pkg_cover +integration_coverage_data="${integration_coveragePkg}"/cover_data + +## 单元测试覆盖率数据目录和文件 +uint_coveragePkg=${cover_pkg}/uint +uint_coverage_data="${uint_coveragePkg}"/cover_data + +## 集成测试和单元测试的合并覆盖率数据目录和文件 +merge_coverage_file=${cover_pkg}/cover_integration_and_unit_test_all.out +merge_coverage_html=${cover_pkg}/cover_integration_and_unit_test_all.html + + +function generate_integration_coverage() { + go tool covdata percent -i="${integration_coverage_data}" > "${integration_coverage_pkg}" + go tool covdata textfmt -i="${integration_coverage_data}" -o "${integration_coverage_file}" + go tool cover -html="${integration_coverage_file}" -o="${integration_coverage_html}" + go tool cover -func "${integration_coverage_file}" > "${integration_coverage_func}" +} + +function merge_coverage() { + generate_integration_coverage + go tool covdata textfmt -i="${integration_coverage_data}","${uint_coverage_data}" -o "${merge_coverage_file}" + go tool cover -html="${merge_coverage_file}" -o="${merge_coverage_html}" +} + +function cover_integration_build() { + sed -i 's/go build -buildmode=pie/go build -cover -buildmode=pie/g' ${project_root}/Makefile + make +} + +function cover_uint_tests() { + mkdir -p ${uint_coverage_data} + go test -mod=vendor -cover -v ./... -args -test.gocoverdir=${uint_coverage_data} +} + +function cover_integration_tests() { + cover_integration_build + mkdir -p "${log_pkg}" + mkdir -p "${integration_coverage_data}" + # 定义字符串列表,包含用例脚本名 + scripts=("netns_test_cover") + + # 遍历列表,依次执行脚本 + for script in "${scripts[@]}" + do + # 拼接project_root和脚本名 + script_path="${test_pkg}/${script}.sh" + ls ${script_path} + if [ $? -ne 0 ]; then + echo "$script_path is not existed" + else + # 执行脚本,将日志保存到${project_root}/log目录中,日志以用例名.log命名 + bash -x "${script_path}" "${build_pkg}" "${integration_coverage_data}" > "${log_pkg}/${script%}.log" 2>&1 + fi + done +} + +#cover_uint_tests +#cover_integration_build +#cover_integration_tests +merge_coverage diff --git a/libnetwork/drivers/common/driver.go b/libnetwork/drivers/common/driver.go index a2158a1..c8280b9 100644 --- a/libnetwork/drivers/common/driver.go +++ b/libnetwork/drivers/common/driver.go @@ -22,16 +22,17 @@ import ( // Driver implement the network driver common options type Driver struct { - nsPath string - ctrName string - hostName string - mac *net.HardwareAddr - ip *net.IPNet - ip6 *net.IPNet - bridge string - bridgeDriver api.BridgeDriver - mtu int - qlen int + nsPath string + ctrName string + hostName string + mac *net.HardwareAddr + ip *net.IPNet + ip6 *net.IPNet + bridge string + bridgeDriver api.BridgeDriver + mtu int + qlen int + vethHostNSPath string } // SetCtrNicName will set the network interface name in container @@ -114,6 +115,16 @@ func (d *Driver) GetQlen() int { return d.qlen } +// SetVethHostNSPath will set the network interface vethHostNSPath +func (d *Driver) SetVethHostNSPath(vethHostNSPath string) { + d.vethHostNSPath = vethHostNSPath +} + +// GetVethHostNSPath will return the network interface vethHostNSPath +func (d *Driver) GetVethHostNSPath() string { + return d.vethHostNSPath +} + // SetBridge will set the bridge name which the nic connected to func (d *Driver) SetBridge(bridgeName string) { d.bridge = bridgeName diff --git a/libnetwork/drivers/common/driver_test.go b/libnetwork/drivers/common/driver_test.go index 7247232..2ee070d 100644 --- a/libnetwork/drivers/common/driver_test.go +++ b/libnetwork/drivers/common/driver_test.go @@ -22,15 +22,16 @@ import ( // TestDriver_GetAndSet tests setter & getter methods func TestDriver_GetAndSet(t *testing.T) { type fields struct { - nsPath string - ctrName string - hostName string - mac *net.HardwareAddr - ip *net.IPNet - ip6 *net.IPNet - bridge string - mtu int - qlen int + nsPath string + ctrName string + hostName string + mac *net.HardwareAddr + ip *net.IPNet + ip6 *net.IPNet + bridge string + mtu int + qlen int + vethHostNSPath string } tests := []struct { name string @@ -52,6 +53,7 @@ func TestDriver_GetAndSet(t *testing.T) { d.SetMtu(tt.fields.mtu) d.SetQlen(tt.fields.qlen) d.SetNsPath(tt.fields.nsPath) + d.SetVethHostNSPath(tt.fields.vethHostNSPath) d.GetBridge() d.GetCtrNicName() @@ -63,6 +65,7 @@ func TestDriver_GetAndSet(t *testing.T) { d.GetQlen() d.GetNsPath() d.GetBridgeDriver() + d.GetVethHostNSPath() }) } } diff --git a/libnetwork/drivers/driver.go b/libnetwork/drivers/driver.go index 86cac7a..58ac632 100644 --- a/libnetwork/drivers/driver.go +++ b/libnetwork/drivers/driver.go @@ -188,6 +188,14 @@ func NicOptionQlen(qlen int) DriverOptions { } } +//NicOptionVethHostNSPath handles interface VethHostNSPath option +func NicOptionVethHostNSPath(vethHostNSPath string) DriverOptions { + return func (d *common.Driver) error { + d.SetVethHostNSPath(strings.TrimSpace(vethHostNSPath)) + return nil + } +} + // NicOptionBridge handles brigde name option func NicOptionBridge(bridge string) DriverOptions { return func(d *common.Driver) error { diff --git a/libnetwork/drivers/veth/driver.go b/libnetwork/drivers/veth/driver.go index a485505..d1b86ad 100644 --- a/libnetwork/drivers/veth/driver.go +++ b/libnetwork/drivers/veth/driver.go @@ -21,6 +21,7 @@ import ( "github.com/docker/libnetwork/netutils" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" "isula.org/syscontainer-tools/libnetwork/drivers/common" "isula.org/syscontainer-tools/libnetwork/nsutils" @@ -79,13 +80,19 @@ func (d *vethDriver) CreateIf() error { PeerName: guestIfName, } d.mutex.Unlock() - if err = netlink.LinkAdd(d.veth); err != nil { - return fmt.Errorf("failed to create veth pairs: %v", err) - } - if err := d.setDefaultVethFeature(guestIfName); err != nil { - return err - } - if err := d.setDefaultVethFeature(hostIfName); err != nil { + // switch to the specified namespace to create a network interface + if err := nsutils.SwitchAndExecute(d.GetVethHostNSPath(), func() error { + if err := netlink.LinkAdd(d.veth); err != nil { + return fmt.Errorf("failed to create veth pairs: %v", err) + } + if err := d.setDefaultVethFeature(guestIfName); err != nil { + return fmt.Errorf("failed to set default features for guest interface: %v", err) + } + if err := d.setDefaultVethFeature(hostIfName); err != nil { + return fmt.Errorf("failed to set default features for host interface: %v", err) + } + return nil + }); err != nil { return err } logrus.Debugf("veth pair (%s, %s) created", hostIfName, guestIfName) @@ -93,18 +100,21 @@ func (d *vethDriver) CreateIf() error { } func (d *vethDriver) DeleteIf() error { - veth, err := netlink.LinkByName(d.GetHostNicName()) - if err != nil { - // As add-nic supports 'update-config-only' option, - // With this flag, syscontainer-tools will update config only, don't add device to container. - // So if device dose not exist on host, ignore it. - if strings.Contains(err.Error(), "Link not found") { - return nil + return nsutils.SwitchAndExecute(d.GetVethHostNSPath(), func() error { + veth, err := netlink.LinkByName(d.GetHostNicName()) + if err != nil { + // As add-nic supports 'update-config-only' option, + // With this flag, syscontainer-tools will update config only, don't add device to container. + // So if device dose not exist on host, ignore it. + if strings.Contains(err.Error(), "Link not found") { + return nil + } + return err } - return err - } - return netlink.LinkDel(veth) + return netlink.LinkDel(veth) + }) + } func (d *vethDriver) setNicConfigure(nic netlink.Link) (rErr error) { @@ -151,88 +161,99 @@ func (d *vethDriver) JoinAndConfigure() (rErr error) { defer func() { if rErr != nil { logrus.Infof("Recover on failure: delete veth(%s)", hostNicName) - nic, err := netlink.LinkByName(hostNicName) - if err != nil { - logrus.Errorf("Recover on failure: failed to get link by name(%q): %v", hostNicName, err) - return - } - if err := netlink.LinkDel(nic); err != nil { - logrus.Errorf("Recover on failure: failed to remove nic(%s): %v", hostNicName, err) + // restore the NIC environment in the specified namespace + if err := nsutils.SwitchAndExecute(d.GetVethHostNSPath(), func() error { + nic, err := netlink.LinkByName(hostNicName) + if err != nil { + return fmt.Errorf("failed to get link by name(%q): %v", hostNicName, err) + } + if err := netlink.LinkDel(nic); err != nil { + return fmt.Errorf("failed to remove nic(%s): %v", hostNicName, err) + } + return nil + }); err != nil { + logrus.Errorf("Recover on failure: %v", err) } } }() - err := nsutils.NsInvoke( - d.GetNsPath(), func(nsFD int) error { - // pre function is executed in host - hostNic, err := netlink.LinkByName(d.veth.Attrs().Name) - if err != nil { - return fmt.Errorf("failed to get link by name %q: %v", d.veth.Attrs().Name, err) - } - ctrNic, err := netlink.LinkByName(d.veth.PeerName) - if err != nil { - return fmt.Errorf("failed to get link by name %q: %v", d.veth.PeerName, err) - } - // down the interface before configuring - if err := netlink.LinkSetDown(hostNic); err != nil { - return fmt.Errorf("failed to set link down: %v", err) - } - if err := netlink.LinkSetDown(ctrNic); err != nil { - return fmt.Errorf("failed to set link down: %v", err) - } + // Move one end of the virtual NIC pair to the network namespace of the container + if err := nsutils.SwitchAndExecute(d.GetVethHostNSPath(), func() error { + hostNic, err := netlink.LinkByName(d.veth.Attrs().Name) + if err != nil { + return fmt.Errorf("failed to get link by name %q: %v", d.veth.Attrs().Name, err) + } + ctrNic, err := netlink.LinkByName(d.veth.PeerName) + if err != nil { + return fmt.Errorf("failed to get link by name %q: %v", d.veth.PeerName, err) + } + // down the interface before configuring + if err := netlink.LinkSetDown(hostNic); err != nil { + return fmt.Errorf("failed to set hostnic down: %v", err) + } + if err := netlink.LinkSetDown(ctrNic); err != nil { + return fmt.Errorf("failed to set link down: %v", err) + } - // move the network interface to the destination - if err := netlink.LinkSetNsFd(ctrNic, nsFD); err != nil { - return fmt.Errorf("failed to set namespace on link %q: %v", d.veth.PeerName, err) - } + // move the network interface to the destination + ctrNs, err := netns.GetFromPath(d.GetNsPath()) + if err != nil { + return fmt.Errorf("failed get network namespace %s: %v", d.GetNsPath(), err) + } + defer ctrNs.Close() + if err := netlink.LinkSetNsFd(ctrNic, int(ctrNs)); err != nil { + return fmt.Errorf("failed to set namespace on link %q: %v", d.veth.PeerName, err) + } - // attach host nic to bridge and configure mtu - if err = netlink.LinkSetMTU(hostNic, d.GetMtu()); err != nil { - return fmt.Errorf("failed to set mtu: %v", err) - } + // attach host nic to bridge and configure mtu + if err = netlink.LinkSetMTU(hostNic, d.GetMtu()); err != nil { + return fmt.Errorf("failed to set mtu: %v", err) + } - if d.GetHostNicName() != "" { - // set iface to user desired name - if err := netlink.LinkSetName(hostNic, d.GetHostNicName()); err != nil { - return fmt.Errorf("failed to rename link %s -> %s: %v", d.veth.Attrs().Name, d.GetHostNicName(), err) - } - hostNicName = d.GetHostNicName() - logrus.Debugf("Rename host link %s -> %s", d.veth.Attrs().Name, d.GetHostNicName()) + if d.GetHostNicName() != "" { + // set iface to user desired name + if err := netlink.LinkSetName(hostNic, d.GetHostNicName()); err != nil { + return fmt.Errorf("failed to rename link %s -> %s: %v", d.veth.Attrs().Name, d.GetHostNicName(), err) } + hostNicName = d.GetHostNicName() + logrus.Debugf("Rename host link %s -> %s", d.veth.Attrs().Name, d.GetHostNicName()) + } - if err = d.AddToBridge(); err != nil { - return fmt.Errorf("failed to add to bridge: %v", err) - } + if err := d.AddToBridge(); err != nil { + return fmt.Errorf("failed to add to bridge: %v", err) + } - if err := netlink.LinkSetUp(hostNic); err != nil { - return fmt.Errorf("failed to set link up: %v", err) - } - return nil - }, func(nsFD int) error { - // post function is executed in container - ctrNic, err := netlink.LinkByName(d.veth.PeerName) - if err != nil { - return fmt.Errorf("failed to get link by name %q: %v", d.veth.PeerName, err) - } + if err := netlink.LinkSetUp(hostNic); err != nil { + return fmt.Errorf("failed to set link up: %v", err) + } + return nil + }); err != nil { + return err + } - // set iface to user desired name - if err := netlink.LinkSetName(ctrNic, d.GetCtrNicName()); err != nil { - return fmt.Errorf("failed to rename link: %v", err) - } - logrus.Debugf("Rename container link %s -> %s", d.veth.PeerName, d.GetCtrNicName()) + // Set vNIC properties in the container's network namespace + return nsutils.SwitchAndExecute(d.GetNsPath(), func() error { + ctrNic, err := netlink.LinkByName(d.veth.PeerName) + if err != nil { + return fmt.Errorf("failed to get link by name %q: %v", d.veth.PeerName, err) + } - if err := d.setNicConfigure(ctrNic); err != nil { - return err - } + // set iface to user desired name + if err := netlink.LinkSetName(ctrNic, d.GetCtrNicName()); err != nil { + return fmt.Errorf("failed to rename link: %v", err) + } + logrus.Debugf("Rename container link %s -> %s", d.veth.PeerName, d.GetCtrNicName()) - // Up the interface. - if err := netlink.LinkSetUp(ctrNic); err != nil { - return fmt.Errorf("failed to set link up: %v", err) - } - return nil - }) + if err := d.setNicConfigure(ctrNic); err != nil { + return err + } - return err + // Up the interface. + if err := netlink.LinkSetUp(ctrNic); err != nil { + return fmt.Errorf("failed to set link up: %v", err) + } + return nil + }) } func (d *vethDriver) Configure() (rErr error) { diff --git a/libnetwork/drivers/veth/driver_test.go b/libnetwork/drivers/veth/driver_test.go new file mode 100644 index 0000000..facb7af --- /dev/null +++ b/libnetwork/drivers/veth/driver_test.go @@ -0,0 +1,245 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2018-2024. All rights reserved. +// syscontainer-tools is licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Description: virtual ethetic network driver +// Author: Jichao Wu +// Create: 2018-01-18 + +package veth + +import ( + "fmt" + "os/exec" + "testing" + + "github.com/docker/libnetwork/netutils" + "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + + "isula.org/syscontainer-tools/libnetwork/drivers/common" + "isula.org/syscontainer-tools/types" +) + +func deleteNs(name string) { + if err := exec.Command("sudo", "ip", "netns", "del", name).Run(); err != nil { + logrus.Errorf("failed to delete the network namespace:%v", err) + } +} +func createNs(name string) error { + if err := exec.Command("sudo", "ip", "netns", "add", name).Run(); err != nil { + return fmt.Errorf("failed to create a new network namespace: %v", err) + } + return nil +} + +func createBridge(name string) error { + if err := netlink.LinkAdd(&netlink.Bridge{LinkAttrs: netlink.LinkAttrs{Name: name, TxQLen: -1}}); err != nil { + return fmt.Errorf("failed to create foo bridge: %v", err) + } + return nil +} + +func deleteBridge(name string) { + l, err := netlink.LinkByName(name) + if err != nil { + logrus.Errorf("failed to find the link %v: %v", name, err) + return + } + if err := netlink.LinkDel(l); err != nil { + logrus.Errorf("failed to delete bridge %v: %v", name, err) + } +} + +// Test_vethDriver_CreateIf tests CreateIf of vethDriver +func Test_vethDriver_CreateIf(t *testing.T) { + const ( + bridgeName = "foo" + containerNs = "testNameSpase" + containerNsPath = "/var/run/netns/" + containerNs + qlen = 1500 + mtu = 1000 + ) + ipNet, err := netlink.ParseIPNet(types.CIDRIpExample1) + if err != nil { + logrus.Errorf("skip this testcase because it failed topoarse ipnet: %v", err) + return + } + + if err := createNs(containerNs); err != nil { + logrus.Errorf("skip this testcase: %v", err) + return + } + defer deleteNs(containerNs) + + if err := createBridge(bridgeName); err != nil { + logrus.Errorf("skip this testcase: %v", err) + return + } + defer deleteBridge(bridgeName) + + tests := []struct { + name string + wantErr bool + wantCreateErr bool + wantJoin bool + wantJoinErr bool + wantDelete bool + wantDeleteErr bool + pre func(d *vethDriver) error + post func(d *vethDriver) + }{ + { + name: "TC1-empty veth namespace & add nic successfully", + pre: func(d *vethDriver) error { + d.SetQlen(qlen) + d.SetMtu(mtu) + d.SetIP(ipNet) + d.SetHostNicName("H") + d.SetCtrNicName("C") + d.SetBridge(bridgeName) + d.SetNsPath(containerNsPath) + return nil + }, + post: func(d *vethDriver) { + if err := netlink.LinkDel(d.veth); err != nil { + logrus.Errorf("failed to delete test veth: %v", err) + } + }, + wantJoin: true, + wantDelete: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &vethDriver{ + Driver: &common.Driver{}, + } + if tt.pre != nil { + if err := tt.pre(d); err != nil { + logrus.Errorf("skip this tc because it failed to execute pre func: %v", err) + return + } + } + defer func() { + if tt.post != nil { + tt.post(d) + } + }() + + if err := d.CreateIf(); (err != nil) != tt.wantCreateErr { + t.Errorf("vethDriver.CreateIf() error = %v, wantErr %v", err, tt.wantCreateErr) + return + } + if tt.wantJoin { + // The NIC is set only after the creation is successful. + if err := d.JoinAndConfigure(); (err != nil) != tt.wantJoinErr { + t.Errorf("vethDriver.JoinAndConfigure() error = %v, wantErr %v", err, tt.wantJoinErr) + return + } + } + if tt.wantDelete { + // The NIC is deleted only after it is successfully configured. + if err := d.DeleteIf(); (err != nil) != tt.wantDeleteErr { + t.Errorf("vethDriver.DeleteIf() error = %v, wantErr %v", err, tt.wantDeleteErr) + } + } + }) + } +} + +// Test_vethDriver_DeleteIf tests DeleteIf of vethDriver +func Test_vethDriver_DeleteIf(t *testing.T) { + randomHostNic, err := netutils.GenerateIfaceName("test", 10) + if err != nil { + logrus.Infof("skip this TC because it failed to get ifname: %v", err) + return + } + tests := []struct { + name string + wantErr bool + pre func(d *vethDriver) + }{ + { + name: "TC1-non existed nic", + pre: func(d *vethDriver) { + d.SetHostNicName(randomHostNic) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &vethDriver{ + Driver: &common.Driver{}, + } + if tt.pre != nil { + tt.pre(d) + } + if err := d.DeleteIf(); (err != nil) != tt.wantErr { + t.Errorf("vethDriver.DeleteIf() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// Test_vethDriver_JoinAndConfigure tests JoinAndConfigure of vethDriver +func Test_vethDriver_JoinAndConfigure(t *testing.T) { + const ( + hostNicName = "testHostNic" + ctrNicName = "testCtrNic" + nonExistctrNicName = "testCtrNic1" + ) + tests := []struct { + name string + wantErr bool + pre func(d *vethDriver) + }{ + { + name: "TC1-empty veth", + wantErr: true, + }, + { + name: "TC2-non existed nic", + pre: func(d *vethDriver) { + d.veth = &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{Name: hostNicName, TxQLen: d.GetQlen()}, + PeerName: ctrNicName, + } + }, + wantErr: true, + }, + { + name: "TC3-host nic existed but ctr nic is not existed", + pre: func(d *vethDriver) { + d.veth = &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{Name: hostNicName, TxQLen: d.GetQlen()}, + PeerName: ctrNicName, + } + if err := netlink.LinkAdd(d.veth); err != nil { + logrus.Errorf("failed to create veth pairs: %v", err) + return + } + d.veth.PeerName = nonExistctrNicName + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &vethDriver{ + Driver: &common.Driver{}, + } + if tt.pre != nil { + tt.pre(d) + } + if err := d.JoinAndConfigure(); (err != nil) != tt.wantErr { + t.Errorf("vethDriver.JoinAndConfigure() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/libnetwork/interfaces.go b/libnetwork/interfaces.go index 121243a..0c255c2 100644 --- a/libnetwork/interfaces.go +++ b/libnetwork/interfaces.go @@ -75,7 +75,8 @@ func AddNicToContainer(nsPath string, config *types.InterfaceConf) (rErr error) drivers.NicOptionMac(config.Mac), drivers.NicOptionMtu(config.Mtu), drivers.NicOptionQlen(config.Qlen), - drivers.NicOptionBridge(config.Bridge)) + drivers.NicOptionBridge(config.Bridge), + drivers.NicOptionVethHostNSPath(config.VethHostNSPath)) if err != nil { return err } @@ -156,8 +157,8 @@ func DelNicFromContainer(nsPath string, config *types.InterfaceConf) error { drivers.NicOptionIP6(config.IP6), drivers.NicOptionMac(config.Mac), drivers.NicOptionMtu(config.Mtu), - drivers.NicOptionBridge(config.Bridge)) - + drivers.NicOptionBridge(config.Bridge), + drivers.NicOptionVethHostNSPath(config.VethHostNSPath)) if err != nil { return err } @@ -184,6 +185,9 @@ func UpdateNic(ctr *container.Container, config *types.InterfaceConf, updateConf return fmt.Errorf("Network interface %s,%s with type %s not exist in container %s", config.HostNicName, config.CtrNicName, config.Type, ctr.Name()) } + if oldConfig.VethHostNSPath != "" { + return fmt.Errorf("failed to update the virtual NIC pair configuration for the specified namespace") + } if config.IP == "" { tmpConfig.IP = oldConfig.IP } else { diff --git a/libnetwork/nsutils/ns_utils.go b/libnetwork/nsutils/ns_utils.go index 2f7ce5d..da42157 100644 --- a/libnetwork/nsutils/ns_utils.go +++ b/libnetwork/nsutils/ns_utils.go @@ -20,6 +20,39 @@ import ( "github.com/vishvananda/netns" ) +// SwitchAndExecute executes the handler in the specified network namespace +func SwitchAndExecute(nsPath string, handler func() error) error { + // If nsPath is empty, execute handler function directly + if nsPath == "" { + return handler() + } + initns, err := netns.Get() + if err != nil { + return fmt.Errorf("failed to get current namespace %v", err) + } + defer initns.Close() + + ns, err := netns.GetFromPath(nsPath) + if err != nil { + return fmt.Errorf("failed to get namespace %s: %v", nsPath, err) + } + defer ns.Close() + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := netns.Set(ns); err != nil { + return err + } + + // Invoked after the namespace switch. + err = handler() + if setErr := netns.Set(initns); setErr != nil { + return fmt.Errorf("failed to set to initial namespace: %v, and handler err is: %v", setErr, err) + } + return err +} + // NsInvoke function is used for setting network outside/inside the container/netns // prefunc is called in the host, and postfunc is used in container func NsInvoke(path string, prefunc func(nsFD int) error, postfunc func(callerFD int) error) error { diff --git a/libnetwork/nsutils/ns_utils_test.go b/libnetwork/nsutils/ns_utils_test.go new file mode 100644 index 0000000..071e277 --- /dev/null +++ b/libnetwork/nsutils/ns_utils_test.go @@ -0,0 +1,121 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2018-2023. All rights reserved. +// syscontainer-tools is licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Description: network interface type +// Author: Jichao Wu +// Create: 2023-07-22 + +package nsutils + +import ( + "fmt" + "os" + "os/exec" + "testing" + + "github.com/sirupsen/logrus" +) + +// TestSwitchAndExecute tests SwitchAndExecute +func TestSwitchAndExecute(t *testing.T) { + const ( + normalFilePath = "/root/test_net" + ) + type args struct { + nsPath string + handler func() error + } + tests := []struct { + name string + args args + pre func(t *testing.T) error + post func(t *testing.T) + wantErr bool + }{ + { + name: "TC1-correct namespace path", + args: args{ + nsPath: "/var/run/netns/myNs", + handler: func() error { + fmt.Println("when nsPath is not empty, handler called") + return nil + }, + }, + pre: func(t *testing.T) error { + cmd := exec.Command("ip", "netns", "add", "myNs") + return cmd.Run() + }, + post: func(t *testing.T) { + cmd := exec.Command("ip", "netns", "delete", "myNs") + err := cmd.Run() + if err != nil { + t.Errorf("failed to delete the network namespace:%v", err) + } + }, + wantErr: false, + }, + { + name: "TC2-empty namespace path", + args: args{ + nsPath: "", + handler: func() error { + fmt.Println("when nsPath is empty, handler called") + return nil + }, + }, + wantErr: false, + }, + { + name: "TC3-non-existed namespace path", + args: args{ + nsPath: "xxx", + handler: func() error { + return nil + }, + }, + wantErr: true, + }, + { + name: "TC4-normal file path", + args: args{ + nsPath: normalFilePath, + handler: func() error { + return nil + }, + }, + pre: func(t *testing.T) error { + file, err := os.Create(normalFilePath) + file.Close() + return err + }, + post: func(t *testing.T) { + if err := os.Remove(normalFilePath); err != nil { + t.Errorf("failed to remove the file %v: %v", normalFilePath, err) + } + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.pre != nil { + if err := tt.pre(t); err != nil { + logrus.Infof("skip TC because it failed to create the network namespace:%v", err) + return + } + } + if err := SwitchAndExecute(tt.args.nsPath, tt.args.handler); (err != nil) != tt.wantErr { + t.Errorf("SwitchAndExecute() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.post != nil { + tt.post(t) + } + }) + } +} diff --git a/network.go b/network.go index 6e32dc4..dea982d 100644 --- a/network.go +++ b/network.go @@ -37,6 +37,10 @@ var addNicCommand = cli.Command{ and configure it as you wanted, then attach to specified bridge. `, Flags: []cli.Flag{ + cli.StringFlag{ + Name: "namespace", + Usage: "set network namespace of the virtual NIC on the host", + }, cli.StringFlag{ Name: "type", Usage: "set network interface type (veth/eth)", @@ -104,15 +108,16 @@ and configure it as you wanted, then attach to specified bridge. } nicConf := &types.InterfaceConf{ - IP: context.String("ip"), - IP6: context.String("ip6"), - Mac: context.String("mac"), - Mtu: context.Int("mtu"), - Type: context.String("type"), - Bridge: context.String("bridge"), - Qlen: context.Int("qlen"), - CtrNicName: ctrNicName, - HostNicName: hostNicName, + IP: context.String("ip"), + IP6: context.String("ip6"), + Mac: context.String("mac"), + Mtu: context.Int("mtu"), + Type: context.String("type"), + Bridge: context.String("bridge"), + Qlen: context.Int("qlen"), + VethHostNSPath: context.String("namespace"), + CtrNicName: ctrNicName, + HostNicName: hostNicName, } if err := types.ValidNetworkConfig(nicConf); err != nil { diff --git a/test/netns_test_cover.sh b/test/netns_test_cover.sh new file mode 100644 index 0000000..d3fbe4a --- /dev/null +++ b/test/netns_test_cover.sh @@ -0,0 +1,302 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. +# syscontainer-tools is licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# Description: network test +# Author: syscontainer-tools Team +# Create: 2024-07-31 + +current_dir=$(cd $(dirname "$0") && pwd) +TOOL_DIR=${current_dir} +if [ -n "$1" ]; then + TOOL_DIR=$1 +fi +TOOL_CLI="${TOOL_DIR}/syscontainer-tools" +HOOK_CLI="${TOOL_DIR}/syscontainer-hooks" + + +COVER_DIR="${current_dir}/cover_data" +if [ -n "$2" ]; then + COVER_DIR="$2" +fi + +testcase_name=$0 +rename_hooks=0 +ns_name="userSpecifiedNS" +another_ns_name="userSpecifiedNS1" +bridge_name="br0" +ctrNic="ctrNic" +hotsNic="hotsNic" +first="172" +sec="17" +third="28" +fourth="5" +mask="24" +ip=$first"."$sec"."$third"."$fourth"/"$mask +mac="00:ff:48:13:90:01" +expect_result="[{\"Ip\":\"$ip\",\"Ip6\":\"\",\"Mac\":\"$mac\",\"Mtu\":1500,\"Qlen\":1000,\"Type\":\"veth\",\"Bridge\":\"$bridge_name\",\"HostNicName\":\"$hotsNic\",\"CtrNicName\":\"$ctrNic\",\"Namespace\":\"/var/run/netns/$ns_name\"}]" +#color code +color_red=$(tput setaf 1 || tput AF 1) +color_green=$(tput setaf 2 || tput AF 2) +color_yellow=$(tput setaf 3 || tput AF 3) +color_reset=$(tput sgr0 || tput me) + +exit_flag=0 + +function echoTxt() { + TXT="[$(date "+%Y-%m-%d-%H-%M-%S")]$1" + COLOR=$2 + if [ "${COLOR}" = "red" ]; then + echo -e "${color_red}${TXT}${color_reset}" + elif [ "${COLOR}" = "green" ]; then + echo -e "${color_green}${TXT}${color_reset}" + elif [ "${COLOR}" = "yellow" ]; then + echo -e "${color_yellow}${TXT}${color_reset}" + else + echo "${TXT}" + fi +} + +function tsm_error() { + txt_str=$1 + echoTxt "$txt_str" red +} + +function tsm_info() { + txt_str=$1 + echoTxt "$txt_str" green +} + +function WaitInspect() { + container=$1 + expect_state=$2 + result="FAIL" + if [ $# -lt 2 ]; then + tsm_error "FAILED: take at lease 2 input parameters" + return 1 + fi + for ((i = 0; i < 30; i++)); do + current_state=$(isula inspect -f '{{.State.Status}}' "$container") + if [ "$current_state" == "$expect_state" ]; then + result="PASS" + break + else + sleep 1 + fi + done + if [ "$result" == "PASS" ]; then + tsm_info "PASS:return $current_state as expected!($3)" + return 0 + else + tsm_error "FAILED:return $current_state not as expected!($3)" + ((exit_flag++)) + return 1 + fi +} + +function skip_test() { + echo "skip this testcase ${testcase_name}" + exit 1 +} + +function check_tools_existed() { + if [ ! -f "${TOOL_CLI}" ]; then + echo "Error: ${TOOL_CLI} not found" + skip_test + fi + if [ ! -f "${HOOK_CLI}" ]; then + echo "Error: ${HOOK_CLI} not found" + skip_test + fi +} + +function fn_check_result_noeq() { + if [ "$1" != "$2" ]; then + tsm_info "PASS:return $1 as expected,not equal $2!($3)" + else + tsm_error "FAILED:return $1 not as expected,equal $2!($3)" + ((exit_flag++)) + fi +} + +function fn_check_result() { + if [ "$1" = "$2" ]; then + tsm_info "PASS:return $1 as expected!($3)" + else + tsm_error "FAILED:return $1 not as expected $2!($3)" + ((exit_flag++)) + fi +} + +function pre() { + check_tools_existed + if [ -f "/var/lib/isulad/hooks/syscontainer-hooks" ]; then + # 存在则备份为syscontainer-hooks.bak + mv "/var/lib/isulad/hooks/syscontainer-hooks" "/var/lib/isulad/hooks/syscontainer-hooks.bak" + rename_hooks=1 + fi + if [ -f "${HOOK_CLI}" ]; then + # 存在则拷贝到/var/lib/isulad/hooks/目录下 + cp "${HOOK_CLI}" "/var/lib/isulad/hooks/" + fi + delete_network_namespace $ns_name + delete_network_namespace $another_ns_name + create_network_namespace ${ns_name} + create_network_namespace ${another_ns_name} + mkdir -p "${COVER_DIR}" + echo "COVER_DIR is ${COVER_DIR}" + echo "TOOL_DIR is ${TOOL_DIR}" +} + +function post() { + if [ -f "/var/lib/isulad/hooks/syscontainer-hooks.bak" ] && [ "$rename_hooks" == "1" ]; then + mv "/var/lib/isulad/hooks/syscontainer-hooks.bak" "/var/lib/isulad/hooks/syscontainer-hooks" + fi + isula rm -f $(isula ps -aq) + delete_network_namespace ${ns_name} + delete_network_namespace ${another_ns_name} +} + +#check wether a nic in the specified container +function check_nic_in_container() { + local nic_name="$1" + local container_id="$2" + + isula exec "$container_id" ip link show | grep "$nic_name" > /dev/null + return $? +} + +#check whether a specified NIC in the specified network namespace. +function check_nic_in_namespace() { + local nic_name="$1" + local ns_name="$2" + + ip netns | grep -q "^$ns_name$" + fn_check_result $? 0 "Network namespace ${ns_name} should exist." + + ip netns exec "$ns_name" ip addr show "$nic_name" > /dev/null 2>&1 + return $? +} + +#create network namespace +function create_network_namespace() { + local NS=$1 + ip netns add "$NS" + + ip netns | grep -q "^$NS$" + if [[ $? -eq 0 ]]; then + echo "Network namespace $NS created successfully." + return 0 + else + echo "Failed to create network namespace $NS." + return 1 + fi +} + +#delete network namespace +function delete_network_namespace() { + local ns_name=$1 + + if [ -z "$ns_name" ]; then + return 1 + fi + + ip netns | grep -q "^$ns_name$" + if [ $? -ne 0 ]; then + return 0 + fi + + ip netns delete "$ns_name" + return $? +} + +function main() { + #start syscontainer + container_id=$(isula run -tid --hook-spec /etc/syscontainer-tools/hookspec.json --system-container --external-rootfs /opt/images/rootfs/root-fs/ none init) + WaitInspect "$container_id" "running" + fn_check_result $? 0 "container start" + + #add bridge to user specifed namespace + ip netns exec "${ns_name}" brctl addbr $bridge_name + fn_check_result $? 0 "add bridge to user specifed namespace" + + #add veth nic + GOCOVERDIR=${COVER_DIR} ${TOOL_CLI} add-nic --type "veth" --namespace /var/run/netns/"${ns_name}" --name ${hotsNic}:${ctrNic} --ip $ip --mac $mac --bridge "${bridge_name}" "${container_id}" + fn_check_result $? 0 "1. add veth nic" + + #check nicName in container + check_nic_in_container ${ctrNic} "$container_id" + fn_check_result $? 0 "2. ${ctrNic} should in the container ${container_id}" + + #check is nicName in specified namespace + check_nic_in_namespace ${hotsNic} "${ns_name}" + fn_check_result $? 0 "3. ${hotsNic} should in the specified namespace ${ns_name}" + + #stop container + isula stop "$container_id" > /dev/null + WaitInspect "${container_id}" "exited" + fn_check_result $? 0 "4. stop container" + + #check is nicName in specified namespace + check_nic_in_namespace ${hotsNic} "${ns_name}" + fn_check_result_noeq $? 0 "5. ${hotsNic} should not in the specified namespace ${ns_name}" + + #restart container + isula start "$container_id" + WaitInspect "${container_id}" "running" + fn_check_result $? 0 "6. container ${container_id} should be running." + + #check is nicName in specified namespace + check_nic_in_namespace ${hotsNic} "${ns_name}" + fn_check_result $? 0 "7. ${hotsNic} should in the specified namespace ${ns_name}" + + #check nicName in container + check_nic_in_container ${ctrNic} "$container_id" + fn_check_result $? 0 "8. ${ctrNic} should in the container ${container_id}" + + #add vethNic with same name + GOCOVERDIR=${COVER_DIR} ${TOOL_CLI} add-nic --type "veth" --namespace /var/run/netns/"${ns_name}" --name ${hotsNic}:${ctrNic} --ip $ip --mac $mac --bridge $bridge_name "$container_id" > /dev/null + fn_check_result_noeq $? 0 "9. can not add vethNic with same name" + + #add bridge to user specifed namespace ${another_ns_name} + ip netns exec ${another_ns_name} brctl addbr $bridge_name + fn_check_result $? 0 "10. add bridge to user specifed namespace ${another_ns_name}" + + #add vethNic with same name but different namespace + GOCOVERDIR=${COVER_DIR} ${TOOL_CLI} add-nic --type "veth" --namespace /var/run/netns/${another_ns_name} --name ${hotsNic}:${ctrNic} --ip $ip --mac $mac --bridge $bridge_name "$container_id" > /dev/null + fn_check_result_noeq $? 0 "11. add vethNic with same name but different namespace" + + #list nic + res=$(GOCOVERDIR=${COVER_DIR} ${TOOL_CLI} list-nic "$container_id") + fn_check_result "$res" "$expect_result" "12. list nic successfully" + + #update nic + GOCOVERDIR=${COVER_DIR} ${TOOL_CLI} update-nic --name ${ctrNic} --mac "00:ff:10:43:13:13" "$container_id" > /dev/null + fn_check_result_noeq $? 0 "13. update nic" + + #remove nic + GOCOVERDIR=${COVER_DIR} ${TOOL_CLI} remove-nic --type veth --name ${hotsNic}:${ctrNic} "$container_id" > /dev/null + fn_check_result $? 0 "14. remove nic" + + #check is nicName in specified namespace + check_nic_in_namespace ${hotsNic} "${ns_name}" + fn_check_result_noeq $? 0 "15. ${hotsNic} should not in the specified namespace ${ns_name}" + + #check nicName in container + check_nic_in_container ${ctrNic} "$container_id" + fn_check_result_noeq $? 0 "16. ${ctrNic} should not in the container ${container_id}" +} + +pre +main +post +if [[ ${exit_flag} -ne 0 ]]; then + tsm_error "FAILED" +else + tsm_info "DONE" +fi diff --git a/types/network.go b/types/network.go index 524e1d5..87bbc5c 100644 --- a/types/network.go +++ b/types/network.go @@ -16,9 +16,12 @@ package types import ( "fmt" "net" + "os" + "path/filepath" "strings" "github.com/vishvananda/netlink" + "isula.org/syscontainer-tools/libnetwork/nsutils" ) const ( @@ -53,15 +56,16 @@ type NamespacePath struct { // InterfaceConf is the network interface config type InterfaceConf struct { - IP string `json:"Ip"` - IP6 string `json:"Ip6"` - Mac string `json:"Mac"` - Mtu int `json:"Mtu"` - Qlen int `json:"Qlen"` - Type string `json:"Type"` - Bridge string `json:"Bridge"` - HostNicName string `json:"HostNicName"` - CtrNicName string `json:"CtrNicName"` + IP string `json:"Ip"` + IP6 string `json:"Ip6"` + Mac string `json:"Mac"` + Mtu int `json:"Mtu"` + Qlen int `json:"Qlen"` + Type string `json:"Type"` + Bridge string `json:"Bridge"` + HostNicName string `json:"HostNicName"` + CtrNicName string `json:"CtrNicName"` + VethHostNSPath string `json:"Namespace,omitempty"` } func (nic *InterfaceConf) String() string { @@ -86,6 +90,10 @@ func IsConflictNic(nic1, nic2 *InterfaceConf) error { if nic1.CtrNicName == nic2.CtrNicName { return fmt.Errorf("interface name conflict: %s", nic1.CtrNicName) } + // The same container cannot have a host network card with the same name + // For example: when the user adds a virtual network card pair and specifies a namespace for + // the host-side network card, the host-side network card name cannot have the same name as + // other host-side network cards added to the container. if nic1.HostNicName == nic2.HostNicName { return fmt.Errorf("interface name conflict: %s", nic1.HostNicName) } @@ -113,6 +121,9 @@ func IsSameNic(obj, src *InterfaceConf) bool { if obj.Mac != src.Mac && obj.Mac != "" { return false } + if obj.VethHostNSPath != src.VethHostNSPath && obj.VethHostNSPath != "" { + return false + } if obj.Mtu != src.Mtu && obj.Mtu != 0 { return false } @@ -198,7 +209,14 @@ func ValidNetworkConfig(conf *InterfaceConf) error { if conf.Bridge == "" { return fmt.Errorf("bridge must be specified") } + conf.VethHostNSPath = strings.TrimSpace(conf.VethHostNSPath) + if err := validNamespace(conf.VethHostNSPath); err != nil { + return err + } case "eth": + if conf.VethHostNSPath != "" { + return fmt.Errorf("for eth type, namespace should be empty") + } if conf.HostNicName == "" { return fmt.Errorf("host nic name input error") } @@ -214,3 +232,23 @@ func ValidNetworkConfig(conf *InterfaceConf) error { } return nil } + +// validNamespace will check if the namespace is valid, existing and accessible +func validNamespace(ns string) error { + if ns == "" { + return nil + } + // check whether the namespace path is an absolute path + if !filepath.IsAbs(ns) { + return fmt.Errorf("the specified namespace %v must be an absolute path", ns) + } + // check whether the namespace path exists + if _, err := os.Stat(ns); os.IsNotExist(err) { + return fmt.Errorf("the specified namespace %v does not exist", ns) + } + // use SwitchAndExecute to check whether the namespace is correct + if err := nsutils.SwitchAndExecute(ns, func() error { return nil }); err != nil { + return fmt.Errorf("invalid network namespace path %v: %v", ns, err) + } + return nil +} diff --git a/types/network_test.go b/types/network_test.go index fecf8b6..479f70f 100644 --- a/types/network_test.go +++ b/types/network_test.go @@ -15,7 +15,11 @@ package types import ( + "os" + "os/exec" "testing" + + "github.com/sirupsen/logrus" ) // TestIsConflictNic tests IsConflictNic @@ -214,6 +218,69 @@ func TestIsConflictNic(t *testing.T) { } } +// TestIsConflictNic tests IsConflictNic with namespace added +func TestIsConflictNicNamespace(t *testing.T) { + const ( + testCtrNicName1 = "ctr1" + testCtrNicName2 = "ctr2" + testHostNicName1 = "host1" + testHostNicName2 = "host2" + testMac1 = "aa:bb:cc:dd:ee:ff" + testIP41 = CIDRIpExample1 + testIP42 = CIDRIpExample2 + testIP61 = CIDRIp6Example1 + testIP62 = CIDRIp6Example2 + testMTU = 1500 + validNsPath = "/var/run/netns/myNs" + validNsPath1 = "/var/run/netns/myNs1" + ) + type args struct { + nic1 *InterfaceConf + nic2 *InterfaceConf + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "TC1-same HostNicName but different VethHostNSPath", + args: args{ + nic1: &InterfaceConf{ + HostNicName: testHostNicName1, + VethHostNSPath: validNsPath, + }, + nic2: &InterfaceConf{ + HostNicName: testHostNicName1, + VethHostNSPath: validNsPath1, + }, + }, + wantErr: true, + }, + { + name: "TC2-same HostNicName and same VethHostNSPath", + args: args{ + nic1: &InterfaceConf{ + HostNicName: testHostNicName1, + VethHostNSPath: validNsPath, + }, + nic2: &InterfaceConf{ + HostNicName: testHostNicName1, + VethHostNSPath: validNsPath, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := IsConflictNic(tt.args.nic1, tt.args.nic2); (err != nil) != tt.wantErr { + t.Errorf("IsConflictNic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + // TestIsSameNic tests IsSameNic func TestIsSameNic(t *testing.T) { const ( @@ -371,6 +438,61 @@ func TestIsSameNic(t *testing.T) { } } +// TestIsSameNic tests IsSameNic with namespace added +func TestIsSameNicNamespace(t *testing.T) { + const ( + testCtrNicName1 = "ctr1" + testCtrNicName2 = "ctr2" + testHostNicName1 = "host1" + testHostNicName2 = "host2" + testMac1 = "aa:bb:cc:dd:ee:ff" + testMac2 = "ff:ee:dd:cc:bb:aa" + testIP41 = CIDRIpExample1 + testIP42 = CIDRIpExample2 + testIP61 = CIDRIp6Example1 + testIP62 = CIDRIp6Example2 + testMTU1 = 1500 + testMTU2 = 1200 + testQlen1 = 500 + testQlen2 = 1000 + testBridge1 = "test1" + testBridge2 = "test2" + testType1 = "eth" + testType2 = "veth" + notEmptyNsPath = "/var/run/netns" + notEmptyNsPath1 = "/var/run/netns1" + ) + type args struct { + obj *InterfaceConf + src *InterfaceConf + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "TC1-obj-notEmptyNsPath && obj-notEmptyNsPath != src-notEmptyNsPath1", + args: args{ + obj: &InterfaceConf{ + VethHostNSPath: notEmptyNsPath, + }, + src: &InterfaceConf{ + VethHostNSPath: notEmptyNsPath1, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsSameNic(tt.args.obj, tt.args.src); got != tt.want { + t.Errorf("IsSameNic() = %v, want %v", got, tt.want) + } + }) + } +} + // TestValidNetworkConfig tests ValidNetworkConfig func TestValidNetworkConfig(t *testing.T) { const ( @@ -512,3 +634,143 @@ func TestValidNetworkConfig(t *testing.T) { }) } } + +// TestValidNetworkConfigNamespace tests ValidNetworkConfig with namespace added +func TestValidNetworkConfigNamespace(t *testing.T) { + const ( + testHostNicName = "host1" + testIP4 = CIDRIpExample1 + testBridge = "test1" + ethType = "eth" + vethType = "veth" + relativeNsPath = "./net" + noExistedNsPath = "/xxx/net" + normalFilePath = "/root/test_net" + validNsPath = "/var/run/netns/myNs" + ) + type args struct { + conf *InterfaceConf + } + tests := []struct { + name string + args args + wantErr bool + pre func(t *testing.T) error + post func(t *testing.T) + }{ + { + name: "TC1-relative namespace path with veth", + args: args{ + conf: &InterfaceConf{ + IP: testIP4, + Type: vethType, + HostNicName: testHostNicName, + Bridge: testBridge, + VethHostNSPath: relativeNsPath, + }, + }, + wantErr: true, + }, + { + name: "TC2-non-existed namespace path with veth", + args: args{ + conf: &InterfaceConf{ + IP: testIP4, + Type: vethType, + HostNicName: testHostNicName, + Bridge: testBridge, + VethHostNSPath: noExistedNsPath, + }, + }, + wantErr: true, + }, + { + name: "TC3-normal file namespace path with veth", + args: args{ + conf: &InterfaceConf{ + IP: testIP4, + Type: vethType, + HostNicName: testHostNicName, + Bridge: testBridge, + VethHostNSPath: normalFilePath, + }, + }, + pre: func(t *testing.T) error { + file, err := os.Create(normalFilePath) + file.Close() + return err + }, + post: func(t *testing.T) { + if err := os.Remove(normalFilePath); err != nil { + t.Errorf("failed to delete a incorrectNsPath: %v", err) + } + }, + wantErr: true, + }, + { + name: "TC4-valid namespace path with veth", + args: args{ + conf: &InterfaceConf{ + IP: testIP4, + Type: vethType, + HostNicName: testHostNicName, + Bridge: testBridge, + VethHostNSPath: validNsPath, + }, + }, + pre: func(t *testing.T) error { + cmd := exec.Command("ip", "netns", "add", "myNs") + return cmd.Run() + }, + post: func(t *testing.T) { + cmd := exec.Command("ip", "netns", "delete", "myNs") + err := cmd.Run() + if err != nil { + t.Errorf("failed to delete the network namespace:%v", err) + } + }, + wantErr: false, + }, + { + name: "TC5-empty namespace path with veth", + args: args{ + conf: &InterfaceConf{ + IP: testIP4, + Type: vethType, + Bridge: testBridge, + HostNicName: testHostNicName, + VethHostNSPath: "", + }, + }, + wantErr: false, + }, + { + name: "TC6-not empty namespace path with eth", + args: args{ + conf: &InterfaceConf{ + IP: testIP4, + Type: ethType, + VethHostNSPath: validNsPath, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.pre != nil { + if err := tt.pre(t); err != nil { + logrus.Infof("skip TC because it failed to create the network namespace:%v", err) + return + } + } + if err := ValidNetworkConfig(tt.args.conf); (err != nil) != tt.wantErr { + + t.Errorf("ValidNetworkConfig() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.post != nil { + tt.post(t) + } + }) + } +} -- 2.33.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2