diff --git a/pkg/cni/cni_suite_test.go b/pkg/cni/cni_suite_test.go new file mode 100644 index 00000000..318146a7 --- /dev/null +++ b/pkg/cni/cni_suite_test.go @@ -0,0 +1,13 @@ +package cni_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestCni(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Cni Suite") +} diff --git a/pkg/cni/plugin_test.go b/pkg/cni/plugin_test.go new file mode 100644 index 00000000..e3a3f5a7 --- /dev/null +++ b/pkg/cni/plugin_test.go @@ -0,0 +1,195 @@ +package cni_test + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + "github.com/kubevirt/macvtap-cni/pkg/cni" + "github.com/kubevirt/macvtap-cni/pkg/util" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/vishvananda/netlink" +) + +const LOWER_DEVICE = "eth0" + +var _ = Describe("Macvtap device plugin", func() { + var originalNS ns.NetNS + var targetNs ns.NetNS + var lowerDevice netlink.Link + var macvtapInterface netlink.Link + var deviceID string + var stdInArgs string + + BeforeEach(func() { + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + targetNs, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + // create lower device + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: LOWER_DEVICE, + }, + }) + Expect(err).NotTo(HaveOccurred()) + lowerDevice, err = netlink.LinkByName(LOWER_DEVICE) + Expect(err).NotTo(HaveOccurred()) + + // create macvtap on top of lower device + macvtapIfaceName := "macvtap0" + _, err = util.CreateMacvtap(macvtapIfaceName, LOWER_DEVICE, "bridge") + Expect(err).NotTo(HaveOccurred()) + macvtapInterface, err = netlink.LinkByName(macvtapIfaceName) + Expect(err).NotTo(HaveOccurred()) + deviceID = macvtapIfaceName + + stdInArgs = fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvtap", + "deviceID": "%s" + }`, macvtapIfaceName) + return nil + }) + + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + // cleanup the original namespace + originalNS.Do(func(ns.NetNS) error { + return netlink.LinkDel(lowerDevice) + }) + Expect(originalNS.Close()).To(Succeed()) + Expect(testutils.UnmountNS(originalNS)).To(Succeed()) + + // cleanup the target namespace + targetNs.Do(func(ns.NetNS) error { + return netlink.LinkDel(macvtapInterface) + }) + Expect(targetNs.Close()).To(Succeed()) + Expect(testutils.UnmountNS(targetNs)).To(Succeed()) + }) + + It("imports a macvtap interface into the target netns", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: deviceID, + StdinData: []byte(stdInArgs), + } + + // import existing interface to target namespace + originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, func() error { return cni.CmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // confirm macvtap is available on target namespace + targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + _, err := netlink.LinkByName(deviceID) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + }) + + It("removes a macvtap interface", func() { + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: deviceID, + StdinData: []byte(stdInArgs), + } + + // import existing interface to target namespace + originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, func() error { return cni.CmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + + err = testutils.CmdDel(args.Netns, args.ContainerID, args.IfName, func() error { return cni.CmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(deviceID) + Expect(err).To(HaveOccurred()) + return nil + }) + }) + + It("imports a macvtap interface into the target netns, and configures the MAC address", func() { + const macAddress = "0a:59:00:dc:6a:e0" + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: deviceID, + StdinData: []byte(stdInArgs), + Args: fmt.Sprintf("MAC=%s", macAddress), + } + + // import existing interface to target namespace + originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, func() error { return cni.CmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // confirm macvtap is available on target namespace, and the correct configurations were applied + targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + link, err := netlink.LinkByName(deviceID) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr.String()).To(Equal(macAddress)) + return nil + }) + }) + + It("imports a macvtap interface into the target netns, and configures the MTU", func() { + const mtu = 1000 + updatedMtuArgs := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvtap", + "deviceID": "%s", + "mtu": %d + }`, deviceID, mtu) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: deviceID, + StdinData: []byte(updatedMtuArgs), + } + + // import existing interface to target namespace + originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, func() error { return cni.CmdAdd(args) }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // confirm macvtap is available on target namespace, and the correct configurations were applied + targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + link, err := netlink.LinkByName(deviceID) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().MTU).To(Equal(mtu)) + return nil + }) + }) +})