diff --git a/plugins/main/ipvlan/README.md b/plugins/main/ipvlan/README.md index 988555ef..f0abc4f5 100644 --- a/plugins/main/ipvlan/README.md +++ b/plugins/main/ipvlan/README.md @@ -27,7 +27,7 @@ Because all ipvlan interfaces share the MAC address with the host interface, DHC * `name` (string, required): the name of the network. * `type` (string, required): "ipvlan". -* `master` (string, required): name of the host interface to enslave. +* `master` (string, required): name of the host interface to enslave or "ipam" to enslave an interface returned by ipam. * `mode` (string, optional): one of "l2", "l3". Defaults to "l2". * `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. @@ -38,3 +38,4 @@ Because all ipvlan interfaces share the MAC address with the host interface, DHC Therefore the container will not be able to reach the host via `ipvlan` interface. Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`). * A single master interface can not be enslaved by both `macvlan` and `ipvlan`. +* For IP allocation schemes that cannot be interface agnostic, master can be set to `ipam`. In this configuration, the ipam plugin is required to return a single interface name for the ipvlan plugin to enslave. diff --git a/plugins/main/ipvlan/ipvlan.go b/plugins/main/ipvlan/ipvlan.go index 74a98863..73cd5a54 100644 --- a/plugins/main/ipvlan/ipvlan.go +++ b/plugins/main/ipvlan/ipvlan.go @@ -138,11 +138,6 @@ func cmdAdd(args *skel.CmdArgs) error { } defer netns.Close() - ipvlanInterface, err := createIpvlan(n, args.IfName, netns) - if err != nil { - return err - } - // run the IPAM plugin and get back the config to apply r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) if err != nil { @@ -157,6 +152,21 @@ func cmdAdd(args *skel.CmdArgs) error { if len(result.IPs) == 0 { return errors.New("IPAM plugin returned missing IP config") } + + if n.Master == "ipam" { + // Use an IPAM supplied master interface + if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" { + n.Master = result.Interfaces[0].Name + } else { + return errors.New("IPAM plugin returned missing master interface") + } + } + + ipvlanInterface, err := createIpvlan(n, args.IfName, netns) + if err != nil { + return err + } + for _, ipc := range result.IPs { // All addresses belong to the ipvlan interface ipc.Interface = current.Int(0) diff --git a/plugins/main/ipvlan/ipvlan_test.go b/plugins/main/ipvlan/ipvlan_test.go index a2b92179..94ac3bf9 100644 --- a/plugins/main/ipvlan/ipvlan_test.go +++ b/plugins/main/ipvlan/ipvlan_test.go @@ -224,4 +224,40 @@ var _ = Describe("ipvlan Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("errors if master should originate from ipam but an ipam interface is not returned", func() { + const IFNAME = "ipvl0" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "ipvlan", + "master": "ipam", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}` + targetNs, err := ns.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, []byte(conf), func() error { + return cmdAdd(args) + }) + Expect(err.Error()).To(Equal("IPAM plugin returned missing master interface")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) })