// Copyright 2017 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "encoding/json" "fmt" "math/rand" "net" "strings" "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types/040" "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" ) type Net struct { Name string `json:"name"` CNIVersion string `json:"cniVersion"` Type string `json:"type,omitempty"` Device string `json:"device"` // Device-Name, something like eth0 or can0 etc. HWAddr string `json:"hwaddr"` // MAC Address of target network interface KernelPath string `json:"kernelpath"` // Kernelpath of the device PCIAddr string `json:"pciBusID"` // PCI Address of target network device IPAM *IPAMConfig `json:"ipam,omitempty"` DNS types.DNS `json:"dns"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` PrevResult types100.Result `json:"-"` } type IPAMConfig struct { Name string Type string `json:"type"` Routes []*types.Route `json:"routes"` Addresses []Address `json:"addresses,omitempty"` DNS types.DNS `json:"dns"` } type IPAMEnvArgs struct { types.CommonArgs IP types.UnmarshallableString `json:"ip,omitempty"` GATEWAY types.UnmarshallableString `json:"gateway,omitempty"` } type Address struct { AddressStr string `json:"address"` Gateway net.IP `json:"gateway,omitempty"` Address net.IPNet Version string } // canonicalizeIP makes sure a provided ip is in standard form func canonicalizeIP(ip *net.IP) error { if ip.To4() != nil { *ip = ip.To4() return nil } else if ip.To16() != nil { *ip = ip.To16() return nil } return fmt.Errorf("IP %s not v4 nor v6", *ip) } // LoadIPAMConfig creates IPAMConfig using json encoded configuration provided // as `bytes`. At the moment values provided in envArgs are ignored so there // is no possibility to overload the json configuration using envArgs func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { n := Net{} if err := json.Unmarshal(bytes, &n); err != nil { return nil, "", err } if n.IPAM == nil { return nil, "", fmt.Errorf("IPAM config missing 'ipam' key") } // Validate all ranges numV4 := 0 numV6 := 0 for i := range n.IPAM.Addresses { ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr) if err != nil { return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err) } n.IPAM.Addresses[i].Address = *addr n.IPAM.Addresses[i].Address.IP = ip if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil { return nil, "", fmt.Errorf("invalid address %d: %s", i, err) } if n.IPAM.Addresses[i].Address.IP.To4() != nil { numV4++ } else { numV6++ } } if envArgs != "" { e := IPAMEnvArgs{} err := types.LoadArgs(envArgs, &e) if err != nil { return nil, "", err } if e.IP != "" { for _, item := range strings.Split(string(e.IP), ",") { ipstr := strings.TrimSpace(item) ip, subnet, err := net.ParseCIDR(ipstr) if err != nil { return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err) } addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} if addr.Address.IP.To4() != nil { numV4++ } else { numV6++ } n.IPAM.Addresses = append(n.IPAM.Addresses, addr) } } if e.GATEWAY != "" { for _, item := range strings.Split(string(e.GATEWAY), ",") { gwip := net.ParseIP(strings.TrimSpace(item)) if gwip == nil { return nil, "", fmt.Errorf("invalid gateway address: %s", item) } for i := range n.IPAM.Addresses { if n.IPAM.Addresses[i].Address.Contains(gwip) { n.IPAM.Addresses[i].Gateway = gwip } } } } } // CNI spec 0.2.0 and below supported only one v4 and v6 address if numV4 > 1 || numV6 > 1 { if ok, _ := version.GreaterThanOrEqualTo(n.CNIVersion, "0.3.0"); !ok { return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion) } } // Copy net name into IPAM so not to drag Net struct around n.IPAM.Name = n.Name return n.IPAM, n.CNIVersion, nil } func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { var err error inject := map[string]interface{}{ "name": name, "cniVersion": cniVersion, } // Add previous plugin result if prevResult != nil { inject["prevResult"] = prevResult } // Ensure every config uses the same name and version config := make(map[string]interface{}) confBytes, err := json.Marshal(orig) if err != nil { return nil, err } err = json.Unmarshal(confBytes, &config) if err != nil { return nil, fmt.Errorf("unmarshal existing network bytes: %s", err) } for key, value := range inject { config[key] = value } newBytes, err := json.Marshal(config) if err != nil { return nil, err } conf := &Net{} if err := json.Unmarshal(newBytes, &conf); err != nil { return nil, fmt.Errorf("error parsing configuration: %s", err) } return conf, nil } type tester interface { expectInterfaces(result types.Result, name, mac, sandbox string) } type testerBase struct{} type testerV10x testerBase type testerV04x testerBase type testerV03x testerBase func newTesterByVersion(version string) tester { switch { case strings.HasPrefix(version, "1.0."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} case strings.HasPrefix(version, "0.3."): return &testerV03x{} default: Fail(fmt.Sprintf("unsupported config version %s", version)) } return nil } func (t *testerV10x) expectInterfaces(result types.Result, name, mac, sandbox string) { // check that the result was sane res, err := types100.NewResultFromResult(result) Expect(err).NotTo(HaveOccurred()) Expect(res.Interfaces).To(Equal([]*types100.Interface{ { Name: name, Mac: mac, Sandbox: sandbox, }, })) } func (t *testerV04x) expectInterfaces(result types.Result, name, mac, sandbox string) { // check that the result was sane res, err := types040.NewResultFromResult(result) Expect(err).NotTo(HaveOccurred()) Expect(res.Interfaces).To(Equal([]*types040.Interface{ { Name: name, Mac: mac, Sandbox: sandbox, }, })) } func (t *testerV03x) expectInterfaces(result types.Result, name, mac, sandbox string) { // check that the result was sane res, err := types040.NewResultFromResult(result) Expect(err).NotTo(HaveOccurred()) Expect(res.Interfaces).To(Equal([]*types040.Interface{ { Name: name, Mac: mac, Sandbox: sandbox, }, })) } var _ = Describe("base functionality", func() { var originalNS, targetNS ns.NetNS var ifname string BeforeEach(func() { var err error originalNS, err = testutils.NewNS() Expect(err).NotTo(HaveOccurred()) targetNS, err = testutils.NewNS() Expect(err).NotTo(HaveOccurred()) ifname = fmt.Sprintf("dummy-%x", rand.Int31()) }) AfterEach(func() { Expect(originalNS.Close()).To(Succeed()) Expect(testutils.UnmountNS(originalNS)).To(Succeed()) Expect(targetNS.Close()).To(Succeed()) Expect(testutils.UnmountNS(targetNS)).To(Succeed()) }) for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} { // Redefine ver inside for scope so real value is picked up by each dynamically defined It() // See Gingkgo's "Patterns for dynamically generating tests" documentation. ver := ver It(fmt.Sprintf("[%s] works with a valid config without IPAM", ver), func() { var origLink netlink.Link // prepare ifname in original namespace _ = 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 }) // call CmdAdd cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device", "device": %q }`, ver, 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 t := newTesterByVersion(ver) t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path()) // assert that dummy0 is now in the target namespace _ = 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)) return nil }) // assert that dummy0 is now NOT in the original namespace anymore _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).To(HaveOccurred()) return nil }) // Check that deleting the device moves it back and restores the name _ = 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 }) }) It(fmt.Sprintf("[%s] ensures CmdDel is idempotent", ver), func() { var ( origLink netlink.Link conflictLink netlink.Link ) // prepare host device in original namespace _ = 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 }) // call CmdAdd cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device", "device": %q }`, ver, 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 t := newTesterByVersion(ver) t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path()) // assert that dummy0 is now in the target namespace _ = 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)) return nil }) // assert that dummy0 is now NOT in the original namespace anymore _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).To(HaveOccurred()) return nil }) // create another conflict host device with same name "dummy0" _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err := netlink.LinkAdd(&netlink.Dummy{ LinkAttrs: netlink.LinkAttrs{ Name: ifname, }, }) Expect(err).NotTo(HaveOccurred()) conflictLink, err = netlink.LinkByName(ifname) Expect(err).NotTo(HaveOccurred()) err = netlink.LinkSetUp(conflictLink) Expect(err).NotTo(HaveOccurred()) return nil }) // try to call CmdDel and fails _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).To(HaveOccurred()) return nil }) // assert container interface "eth0" still exists in target namespace _ = 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)) return nil }) // remove the conflict host device _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err = netlink.LinkDel(conflictLink) Expect(err).NotTo(HaveOccurred()) return nil }) // try to call CmdDel and succeed _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) return nil }) // assert that dummy0 is now back in the original namespace _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).NotTo(HaveOccurred()) return nil }) }) It(fmt.Sprintf("Works with a valid %s config with IPAM", ver), func() { var origLink netlink.Link // prepare ifname in original namespace _ = 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 }) // call CmdAdd targetIP := "10.10.0.1/24" cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device", "ipam": { "type": "static", "addresses": [ { "address":"`+targetIP+`", "gateway": "10.10.0.254" }] }, "device": %q }`, ver, 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 t := newTesterByVersion(ver) t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path()) // assert that dummy0 is now in the target namespace _ = 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 }) // assert that dummy0 is now NOT in the original namespace anymore _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).To(HaveOccurred()) return nil }) // Check that deleting the device moves it back and restores the name _ = 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 }) }) It(fmt.Sprintf("[%s] fails an invalid config", ver), func() { conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device" }`, ver) args := &skel.CmdArgs{ ContainerID: "dummy", Netns: originalNS.Path(), IfName: ifname, StdinData: []byte(conf), } _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)) }) It(fmt.Sprintf("[%s] works with a valid config without IPAM", ver), func() { var origLink netlink.Link // prepare ifname in original namespace _ = 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 }) // call CmdAdd cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device", "device": %q }`, ver, 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 t := newTesterByVersion(ver) t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path()) // assert that dummy0 is now in the target namespace _ = 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)) return nil }) // assert that dummy0 is now NOT in the original namespace anymore _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).To(HaveOccurred()) return nil }) if testutils.SpecVersionHasCHECK(ver) { // call CmdCheck n := &Net{} err = json.Unmarshal([]byte(conf), &n) Expect(err).NotTo(HaveOccurred()) newConf, err := buildOneConfig("testConfig", ver, n, resI) Expect(err).NotTo(HaveOccurred()) confString, err := json.Marshal(newConf) Expect(err).NotTo(HaveOccurred()) args.StdinData = confString // CNI Check host-device in the target namespace err = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) }) }) Expect(err).NotTo(HaveOccurred()) } // Check that deleting the device moves it back and restores the name _ = 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 }) }) It(fmt.Sprintf("Works with a valid %s config with IPAM", ver), func() { var origLink netlink.Link // prepare ifname in original namespace _ = 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 }) // call CmdAdd targetIP := "10.10.0.1/24" cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device", "ipam": { "type": "static", "addresses": [ { "address":"`+targetIP+`", "gateway": "10.10.0.254" }] }, "device": %q }`, ver, 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 t := newTesterByVersion(ver) t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path()) // assert that dummy0 is now in the target namespace _ = 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 }) // assert that dummy0 is now NOT in the original namespace anymore _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).To(HaveOccurred()) return nil }) // call CmdCheck n := &Net{} err = json.Unmarshal([]byte(conf), &n) Expect(err).NotTo(HaveOccurred()) n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "") Expect(err).NotTo(HaveOccurred()) if testutils.SpecVersionHasCHECK(ver) { newConf, err := buildOneConfig("testConfig", ver, n, resI) Expect(err).NotTo(HaveOccurred()) confString, err := json.Marshal(newConf) Expect(err).NotTo(HaveOccurred()) args.StdinData = confString // CNI Check host-device in the target namespace err = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) }) }) Expect(err).NotTo(HaveOccurred()) } // Check that deleting the device moves it back and restores the name _ = 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 }) }) It(fmt.Sprintf("Test idempotence of CmdDel with %s config", ver), func() { var ( origLink netlink.Link conflictLink netlink.Link ) // prepare host device in original namespace _ = 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 }) // call CmdAdd cniName := "eth0" conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "cni-plugin-host-device-test", "type": "host-device", "device": %q }`, ver, 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 t := newTesterByVersion(ver) t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path()) // assert that dummy0 is now in the target namespace _ = 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)) return nil }) // assert that dummy0 is now NOT in the original namespace anymore _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).To(HaveOccurred()) return nil }) // create another conflict host device with same name "dummy0" _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err := netlink.LinkAdd(&netlink.Dummy{ LinkAttrs: netlink.LinkAttrs{ Name: ifname, }, }) Expect(err).NotTo(HaveOccurred()) conflictLink, err = netlink.LinkByName(ifname) Expect(err).NotTo(HaveOccurred()) err = netlink.LinkSetUp(conflictLink) Expect(err).NotTo(HaveOccurred()) return nil }) // try to call CmdDel and fails _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).To(HaveOccurred()) return nil }) // assert container interface "eth0" still exists in 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)) return nil }) Expect(err).NotTo(HaveOccurred()) // remove the conflict host device _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err = netlink.LinkDel(conflictLink) Expect(err).NotTo(HaveOccurred()) return nil }) // try to call CmdDel and succeed _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() err = testutils.CmdDelWithArgs(args, func() error { return cmdDel(args) }) Expect(err).NotTo(HaveOccurred()) return nil }) // assert that dummy0 is now back in the original namespace _ = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() _, err := netlink.LinkByName(ifname) Expect(err).NotTo(HaveOccurred()) return nil }) }) } })