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.
This commit is contained in:
Sebastian Sch 2019-04-29 00:26:47 +03:00
parent 44297f6ba3
commit af692de1b8
3 changed files with 166 additions and 49 deletions

View File

@ -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

View File

@ -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 := &current.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.

View File

@ -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"