From af692de1b836717493b77c017ea55191a441cbf4 Mon Sep 17 00:00:00 2001 From: Sebastian Sch Date: Mon, 29 Apr 2019 00:26:47 +0300 Subject: [PATCH] Allow to configure empty ipam for macvlan This PR add the option to configure an empty ipam for the macvlan cni plugin. When using the macvlan cni plugin with an empty ipam the requeted pod will get the macvlan interface but without any ip address. One of the use cases for this feature is for projects that runs a dhcp server inside the pod like KubeVirt. In KubeVirt we need to let the vm running inside the pod to make the dhcp request so it will be able to make a release an renew request when needed. --- plugins/main/macvlan/README.md | 2 +- plugins/main/macvlan/macvlan.go | 130 +++++++++++++++++---------- plugins/main/macvlan/macvlan_test.go | 83 +++++++++++++++++ 3 files changed, 166 insertions(+), 49 deletions(-) diff --git a/plugins/main/macvlan/README.md b/plugins/main/macvlan/README.md index 5d17c556..223a06fa 100644 --- a/plugins/main/macvlan/README.md +++ b/plugins/main/macvlan/README.md @@ -26,7 +26,7 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi * `master` (string, required): name of the host interface to enslave * `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge". * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. -* `ipam` (dictionary, required): IPAM configuration to be used for this network. +* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary. ## Notes diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index 0fb7e9c7..d4628e42 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -169,6 +169,8 @@ func cmdAdd(args *skel.CmdArgs) error { return err } + isLayer3 := n.IPAM.Type != "" + netns, err := ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", netns, err) @@ -189,56 +191,80 @@ func cmdAdd(args *skel.CmdArgs) error { } }() - // run the IPAM plugin and get back the config to apply - r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) - if err != nil { - return err - } + // Assume L2 interface only + result := ¤t.Result{CNIVersion: cniVersion, Interfaces: []*current.Interface{macvlanInterface}} - // Invoke ipam del if err to avoid ip leak - defer func() { + if isLayer3 { + // run the IPAM plugin and get back the config to apply + r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) if err != nil { - os.Setenv("CNI_COMMAND", "DEL") - ipam.ExecDel(n.IPAM.Type, args.StdinData) - os.Setenv("CNI_COMMAND", "ADD") - } - }() - - // Convert whatever the IPAM result was into the current Result type - result, err := current.NewResultFromResult(r) - if err != nil { - return err - } - - if len(result.IPs) == 0 { - return errors.New("IPAM plugin returned missing IP config") - } - result.Interfaces = []*current.Interface{macvlanInterface} - - for _, ipc := range result.IPs { - // All addresses apply to the container macvlan interface - ipc.Interface = current.Int(0) - } - - err = netns.Do(func(_ ns.NetNS) error { - if err := ipam.ConfigureIface(args.IfName, result); err != nil { return err } - contVeth, err := net.InterfaceByName(args.IfName) + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + os.Setenv("CNI_COMMAND", "DEL") + ipam.ExecDel(n.IPAM.Type, args.StdinData) + os.Setenv("CNI_COMMAND", "ADD") + } + }() + + // Convert whatever the IPAM result was into the current Result type + ipamResult, err := current.NewResultFromResult(r) if err != nil { - return fmt.Errorf("failed to look up %q: %v", args.IfName, err) + return err } - for _, ipc := range result.IPs { - if ipc.Version == "4" { - _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) - } + if len(ipamResult.IPs) == 0 { + return errors.New("IPAM plugin returned missing IP config") + } + + result.IPs = ipamResult.IPs + result.Routes = ipamResult.Routes + + for _, ipc := range result.IPs { + // All addresses apply to the container macvlan interface + ipc.Interface = current.Int(0) + } + + err = netns.Do(func(_ ns.NetNS) error { + if err := ipam.ConfigureIface(args.IfName, result); err != nil { + return err + } + + contVeth, err := net.InterfaceByName(args.IfName) + if err != nil { + return fmt.Errorf("failed to look up %q: %v", args.IfName, err) + } + + for _, ipc := range result.IPs { + if ipc.Version == "4" { + _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) + } + } + return nil + }) + if err != nil { + return err + } + } else { + // For L2 just change interface status to up + err = netns.Do(func(_ ns.NetNS) error { + macvlanInterfaceLink, err := netlink.LinkByName(args.IfName) + if err != nil { + return fmt.Errorf("failed to find interface name %q: %v", macvlanInterface.Name, err) + } + + if err := netlink.LinkSetUp(macvlanInterfaceLink); err != nil { + return fmt.Errorf("failed to set %q UP: %v", args.IfName, err) + } + + return nil + }) + if err != nil { + return err } - return nil - }) - if err != nil { - return err } result.DNS = n.DNS @@ -252,9 +278,13 @@ func cmdDel(args *skel.CmdArgs) error { return err } - err = ipam.ExecDel(n.IPAM.Type, args.StdinData) - if err != nil { - return err + isLayer3 := n.IPAM.Type != "" + + if isLayer3 { + err = ipam.ExecDel(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } } if args.Netns == "" { @@ -285,16 +315,20 @@ func cmdCheck(args *skel.CmdArgs) error { if err != nil { return err } + isLayer3 := n.IPAM.Type != "" + netns, err := ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) } defer netns.Close() - // run the IPAM plugin and get back the config to apply - err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) - if err != nil { - return err + if isLayer3 { + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } } // Parse previous result. diff --git a/plugins/main/macvlan/macvlan_test.go b/plugins/main/macvlan/macvlan_test.go index d76540ee..fa2eefc3 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -286,6 +286,89 @@ var _ = Describe("macvlan Operations", func() { }) + It("configures and deconfigures a l2 macvlan link with ADD/DEL", func() { + const IFNAME = "macvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvlan", + "master": "%s", + "ipam": {} +}`, MASTER_NAME) + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(0)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(0)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + It("configures and deconfigures a cniVersion 0.4.0 macvlan link with ADD/DEL", func() { const IFNAME = "macvl0"