From 344d343431c014727dcb3abe9eba02e3d7f3e6a9 Mon Sep 17 00:00:00 2001 From: Bruce Ma Date: Sat, 18 May 2019 13:58:33 +0800 Subject: [PATCH 1/2] bandwidth: get bandwidth interface in host ns through container interface Signed-off-by: Bruce Ma --- plugins/meta/bandwidth/main.go | 49 ++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/plugins/meta/bandwidth/main.go b/plugins/meta/bandwidth/main.go index 6d800589..d967c932 100644 --- a/plugins/meta/bandwidth/main.go +++ b/plugins/meta/bandwidth/main.go @@ -17,7 +17,6 @@ package main import ( "crypto/sha1" "encoding/json" - "errors" "fmt" "github.com/vishvananda/netlink" @@ -28,6 +27,7 @@ import ( "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ip" + "github.com/containernetworking/plugins/pkg/ns" bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) @@ -130,21 +130,35 @@ func getMTU(deviceName string) (int, error) { return link.Attrs().MTU, nil } -func getHostInterface(interfaces []*current.Interface) (*current.Interface, error) { +// get the veth peer of container interface in host namespace +func getHostInterface(interfaces []*current.Interface, containerIfName string, netns ns.NetNS) (*current.Interface, error) { if len(interfaces) == 0 { - return nil, errors.New("no interfaces provided") + return nil, fmt.Errorf("no interfaces provided") } + // get veth peer index of container interface + var peerIndex int var err error + _ = netns.Do(func(_ ns.NetNS) error { + _, peerIndex, err = ip.GetVethPeerIfindex(containerIfName) + return nil + }) + if peerIndex <= 0 { + return nil, fmt.Errorf("container interface %s has no veth peer: %v", containerIfName, err) + } + + // find host interface by index + link, err := netlink.LinkByIndex(peerIndex) + if err != nil { + return nil, fmt.Errorf("veth peer with index %d is not in host ns", peerIndex) + } for _, iface := range interfaces { - if iface.Sandbox == "" { // host interface - _, _, err = ip.GetVethPeerIfindex(iface.Name) - if err == nil { - return iface, err - } + if iface.Sandbox == "" && iface.Name == link.Attrs().Name { + return iface, nil } } - return nil, errors.New(fmt.Sprintf("no host interface found. last error: %s", err)) + + return nil, fmt.Errorf("no veth peer of container interface found in host ns") } func cmdAdd(args *skel.CmdArgs) error { @@ -166,7 +180,14 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return fmt.Errorf("could not convert result to current version: %v", err) } - hostInterface, err := getHostInterface(result.Interfaces) + + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", netns, err) + } + defer netns.Close() + + hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns) if err != nil { return err } @@ -266,7 +287,13 @@ func cmdCheck(args *skel.CmdArgs) error { return fmt.Errorf("could not convert result to current version: %v", err) } - hostInterface, err := getHostInterface(result.Interfaces) + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", netns, err) + } + defer netns.Close() + + hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns) if err != nil { return err } From d35c96dda6b87a99b1bb5c935e8cefb971920269 Mon Sep 17 00:00:00 2001 From: Bruce Ma Date: Mon, 20 May 2019 22:44:21 +0800 Subject: [PATCH 2/2] bandwidth: add testcases for func getHostInterface Signed-off-by: Bruce Ma --- .../meta/bandwidth/bandwidth_linux_test.go | 251 ++++++++++++++++++ .../meta/bandwidth/bandwidth_suite_test.go | 58 ++++ 2 files changed, 309 insertions(+) diff --git a/plugins/meta/bandwidth/bandwidth_linux_test.go b/plugins/meta/bandwidth/bandwidth_linux_test.go index f09980fa..2cc0796a 100644 --- a/plugins/meta/bandwidth/bandwidth_linux_test.go +++ b/plugins/meta/bandwidth/bandwidth_linux_test.go @@ -621,6 +621,257 @@ var _ = Describe("bandwidth test", func() { }) }) + Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() { + It("Should work with multiple host veth interfaces", func() { + conf := `{ + "cniVersion": "0.4.0", + "name": "cni-plugin-bandwidth-test", + "type": "bandwidth", + "ingressRate": 8, + "ingressBurst": 8, + "egressRate": 16, + "egressBurst": 8, + "prevResult": { + "interfaces": [ + { + "name": "%s", + "sandbox": "" + }, + { + "name": "%s", + "sandbox": "" + }, + { + "name": "%s", + "sandbox": "" + }, + { + "name": "%s", + "sandbox": "%s" + } + ], + "ips": [ + { + "version": "4", + "address": "%s/24", + "gateway": "10.0.0.1", + "interface": 1 + } + ], + "routes": [] + } +}` + + // create veth peer in host ns + vethName, peerName := "host-veth-peer1", "host-veth-peer2" + createVethInOneNs(hostNs.Path(), vethName, peerName) + + conf = fmt.Sprintf(conf, vethName, peerName, hostIfname, containerIfname, containerNs.Path(), containerIP.String()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: containerNs.Path(), + IfName: containerIfname, + StdinData: []byte(conf), + } + + Expect(hostNs.Do(func(netNS ns.NetNS) error { + defer GinkgoRecover() + r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) }) + Expect(err).NotTo(HaveOccurred(), string(out)) + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(result.Interfaces).To(HaveLen(5)) + Expect(result.Interfaces[4].Name).To(Equal(ifbDeviceName)) + Expect(result.Interfaces[4].Sandbox).To(Equal("")) + + ifbLink, err := netlink.LinkByName(ifbDeviceName) + Expect(err).NotTo(HaveOccurred()) + Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU)) + + qdiscs, err := netlink.QdiscList(ifbLink) + Expect(err).NotTo(HaveOccurred()) + + Expect(qdiscs).To(HaveLen(1)) + Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index)) + + Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) + Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) + + hostVethLink, err := netlink.LinkByName(hostIfname) + Expect(err).NotTo(HaveOccurred()) + + qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0)) + Expect(err).NotTo(HaveOccurred()) + + Expect(qdiscFilters).To(HaveLen(1)) + Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index)) + + return nil + })).To(Succeed()) + + Expect(hostNs.Do(func(n ns.NetNS) error { + defer GinkgoRecover() + + ifbLink, err := netlink.LinkByName(hostIfname) + Expect(err).NotTo(HaveOccurred()) + + qdiscs, err := netlink.QdiscList(ifbLink) + Expect(err).NotTo(HaveOccurred()) + + Expect(qdiscs).To(HaveLen(2)) + Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index)) + + Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{})) + Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) + return nil + })).To(Succeed()) + + }) + + It("Should fail when container interface has no veth peer", func() { + conf := `{ + "cniVersion": "0.4.0", + "name": "cni-plugin-bandwidth-test", + "type": "bandwidth", + "ingressRate": 8, + "ingressBurst": 8, + "egressRate": 16, + "egressBurst": 8, + "prevResult": { + "interfaces": [ + { + "name": "%s", + "sandbox": "" + }, + { + "name": "%s", + "sandbox": "%s" + } + ], + "ips": [ + { + "version": "4", + "address": "%s/24", + "gateway": "10.0.0.1", + "interface": 1 + } + ], + "routes": [] + } +}` + + // create a macvlan device to be container interface + macvlanContainerIfname := "container-macv" + createMacvlan(containerNs.Path(), containerIfname, macvlanContainerIfname) + + conf = fmt.Sprintf(conf, hostIfname, macvlanContainerIfname, containerNs.Path(), containerIP.String()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: containerNs.Path(), + IfName: macvlanContainerIfname, + StdinData: []byte(conf), + } + + Expect(hostNs.Do(func(netNS ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) }) + Expect(err).To(HaveOccurred()) + + return nil + })).To(Succeed()) + }) + + It("Should fail when preResult has no interfaces", func() { + conf := `{ + "cniVersion": "0.4.0", + "name": "cni-plugin-bandwidth-test", + "type": "bandwidth", + "ingressRate": 8, + "ingressBurst": 8, + "egressRate": 16, + "egressBurst": 8, + "prevResult": { + "interfaces": [], + "ips": [], + "routes": [] + } +}` + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: containerNs.Path(), + IfName: "eth0", + StdinData: []byte(conf), + } + + Expect(hostNs.Do(func(netNS ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) }) + Expect(err).To(HaveOccurred()) + + return nil + })).To(Succeed()) + }) + + It("Should fail when veth peer of container interface does not match any of host interfaces in preResult", func() { + conf := `{ + "cniVersion": "0.4.0", + "name": "cni-plugin-bandwidth-test", + "type": "bandwidth", + "ingressRate": 8, + "ingressBurst": 8, + "egressRate": 16, + "egressBurst": 8, + "prevResult": { + "interfaces": [ + { + "name": "%s", + "sandbox": "" + }, + { + "name": "%s", + "sandbox": "%s" + } + ], + "ips": [ + { + "version": "4", + "address": "%s/24", + "gateway": "10.0.0.1", + "interface": 1 + } + ], + "routes": [] + } +}` + + // fake a non-exist host interface name + fakeHostIfname := fmt.Sprintf("%s-fake", hostIfname) + + conf = fmt.Sprintf(conf, fakeHostIfname, containerIfname, containerNs.Path(), containerIP.String()) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: containerNs.Path(), + IfName: containerIfname, + StdinData: []byte(conf), + } + + Expect(hostNs.Do(func(netNS ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) }) + Expect(err).To(HaveOccurred()) + + return nil + })).To(Succeed()) + }) + }) + Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() { var ptpConf string var rateInBits int diff --git a/plugins/meta/bandwidth/bandwidth_suite_test.go b/plugins/meta/bandwidth/bandwidth_suite_test.go index d59bb594..594cd9f3 100644 --- a/plugins/meta/bandwidth/bandwidth_suite_test.go +++ b/plugins/meta/bandwidth/bandwidth_suite_test.go @@ -199,3 +199,61 @@ func createVeth(hostNamespace string, hostVethIfName string, containerNamespace Expect(err).NotTo(HaveOccurred()) } + +func createVethInOneNs(namespace, vethName, peerName string) { + vethDeviceRequest := &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: vethName, + Flags: net.FlagUp, + }, + PeerName: peerName, + } + + netNS, err := ns.GetNS(namespace) + Expect(err).NotTo(HaveOccurred()) + + err = netNS.Do(func(_ ns.NetNS) error { + if err := netlink.LinkAdd(vethDeviceRequest); err != nil { + return fmt.Errorf("failed to create veth pair: %v", err) + } + + _, err := netlink.LinkByName(peerName) + if err != nil { + return fmt.Errorf("failed to find newly-created veth device %q: %v", peerName, err) + } + return nil + }) + Expect(err).NotTo(HaveOccurred()) +} + +func createMacvlan(namespace, master, macvlanName string) { + netNS, err := ns.GetNS(namespace) + Expect(err).NotTo(HaveOccurred()) + + err = netNS.Do(func(_ ns.NetNS) error { + m, err := netlink.LinkByName(master) + if err != nil { + return fmt.Errorf("failed to lookup master %q: %v", master, err) + } + + macvlanDeviceRequest := &netlink.Macvlan{ + LinkAttrs: netlink.LinkAttrs{ + MTU: m.Attrs().MTU, + Name: macvlanName, + ParentIndex: m.Attrs().Index, + }, + Mode: netlink.MACVLAN_MODE_BRIDGE, + } + + if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil { + return fmt.Errorf("failed to create macvlan device: %s", err) + } + + _, err = netlink.LinkByName(macvlanName) + if err != nil { + return fmt.Errorf("failed to find newly-created macvlan device %q: %v", macvlanName, err) + } + return nil + }) + Expect(err).NotTo(HaveOccurred()) +}