syscontainer-tools/0012-support-Vnic-specifying-network-namespace.patch
武积超 4355181e83 syscontainer-tools: support-Vnic-specifying-network-namespace
(cherry picked from commit 49fff18d7df0871512419c98eec352a4f6b42282)
2024-09-10 19:34:44 +08:00

1683 lines
52 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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