diff --git a/plugins/main/host-device/host-device.go b/plugins/main/host-device/host-device.go index 06e3b9e5..437e2440 100644 --- a/plugins/main/host-device/host-device.go +++ b/plugins/main/host-device/host-device.go @@ -17,6 +17,7 @@ package main import ( "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -28,10 +29,12 @@ import ( "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" "github.com/vishvananda/netlink" ) +//NetConf for host-device config, look the README to learn how to use those parameters type NetConf struct { types.NetConf Device string `json:"device"` // Device-Name, something like eth0 or can0 etc. @@ -77,11 +80,57 @@ func cmdAdd(args *skel.CmdArgs) error { if err != nil { return fmt.Errorf("failed to move link %v", err) } + + // run the IPAM plugin and get back the config to apply + if cfg.IPAM.Type != "" { + r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData) + if err != nil { + return err + } + + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ipam.ExecDel(cfg.IPAM.Type, args.StdinData) + } + }() + + // 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{{ + Name: contDev.Attrs().Name, + Mac: contDev.Attrs().HardwareAddr.String(), + Sandbox: containerNs.Path(), + }} + for _, ipc := range result.IPs { + // All addresses apply to the container interface (move from host) + ipc.Interface = current.Int(0) + } + + err = containerNs.Do(func(_ ns.NetNS) error { + if err := ipam.ConfigureIface(args.IfName, result); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + return printLink(contDev, cfg.CNIVersion, containerNs) } func cmdDel(args *skel.CmdArgs) error { - _, err := loadConf(args.StdinData) + cfg, err := loadConf(args.StdinData) if err != nil { return err } @@ -98,6 +147,12 @@ func cmdDel(args *skel.CmdArgs) error { return err } + if cfg.IPAM.Type != "" { + if err := ipam.ExecDel(cfg.IPAM.Type, args.StdinData); err != nil { + return err + } + } + return nil } diff --git a/plugins/main/host-device/host-device_test.go b/plugins/main/host-device/host-device_test.go index bbecdec7..a894b4b7 100644 --- a/plugins/main/host-device/host-device_test.go +++ b/plugins/main/host-device/host-device_test.go @@ -44,7 +44,7 @@ var _ = Describe("base functionality", func() { originalNS.Close() }) - It("Works with a valid config", func() { + It("Works with a valid config without IPAM", func() { var origLink netlink.Link // prepare ifname in original namespace @@ -68,7 +68,7 @@ var _ = Describe("base functionality", func() { targetNS, err := testutils.NewNS() Expect(err).NotTo(HaveOccurred()) - CNI_IFNAME := "eth0" + cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "0.3.0", "name": "cni-plugin-host-device-test", @@ -78,7 +78,7 @@ var _ = Describe("base functionality", func() { args := &skel.CmdArgs{ ContainerID: "dummy", Netns: targetNS.Path(), - IfName: CNI_IFNAME, + IfName: cniName, StdinData: []byte(conf), } var resI types.Result @@ -95,7 +95,7 @@ var _ = Describe("base functionality", func() { Expect(err).NotTo(HaveOccurred()) Expect(res.Interfaces).To(Equal([]*current.Interface{ { - Name: CNI_IFNAME, + Name: cniName, Mac: origLink.Attrs().HardwareAddr.String(), Sandbox: targetNS.Path(), }, @@ -104,7 +104,7 @@ var _ = Describe("base functionality", func() { // assert that dummy0 is now in the target namespace err = targetNS.Do(func(ns.NetNS) error { defer GinkgoRecover() - link, err := netlink.LinkByName(CNI_IFNAME) + link, err := netlink.LinkByName(cniName) Expect(err).NotTo(HaveOccurred()) Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) return nil @@ -136,6 +136,114 @@ var _ = Describe("base functionality", func() { }) + It("Works with a valid config with IPAM", func() { + var origLink netlink.Link + + // prepare ifname in original namespace + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err := netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: ifname, + }, + }) + Expect(err).NotTo(HaveOccurred()) + origLink, err = netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(origLink) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // call CmdAdd + targetNS, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + targetIP := "10.10.0.1/24" + cniName := "eth0" + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.0", + "name": "cni-plugin-host-device-test", + "type": "host-device", + "ipam": { + "type": "static", + "addresses": [ + { + "address":"`+targetIP+`", + "gateway": "10.10.0.254" + }] + }, + "device": %q + }`, ifname) + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: cniName, + StdinData: []byte(conf), + } + var resI types.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + var err error + resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + // check that the result was sane + res, err := current.NewResultFromResult(resI) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Interfaces).To(Equal([]*current.Interface{ + { + Name: cniName, + Mac: origLink.Attrs().HardwareAddr.String(), + Sandbox: targetNS.Path(), + }, + })) + + // assert that dummy0 is now in the target namespace + err = targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + link, err := netlink.LinkByName(cniName) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) + + //get the IP address of the interface in the target namespace + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + Expect(err).NotTo(HaveOccurred()) + addr := addrs[0].IPNet.String() + //assert that IP address is what we set + Expect(addr).To(Equal(targetIP)) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // assert that dummy0 is now NOT in the original namespace anymore + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + _, err := netlink.LinkByName(ifname) + Expect(err).To(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that deleting the device moves it back and restores the name + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + err = testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + + _, err := netlink.LinkByName(ifname) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + }) + It("fails an invalid config", func() { conf := `{ "cniVersion": "0.3.0",