1193 lines
33 KiB
Go
1193 lines
33 KiB
Go
// 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"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/containernetworking/cni/pkg/skel"
|
|
"github.com/containernetworking/cni/pkg/types"
|
|
types040 "github.com/containernetworking/cni/pkg/types/040"
|
|
types100 "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)
|
|
expectDpdkInterfaceIP(result types.Result, ipAddress 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 *testerV10x) expectDpdkInterfaceIP(result types.Result, ipAddress string) {
|
|
// check that the result was sane
|
|
res, err := types100.NewResultFromResult(result)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(len(res.Interfaces)).To(Equal(0))
|
|
Expect(len(res.IPs)).To(Equal(1))
|
|
Expect(res.IPs[0].Address.String()).To(Equal(ipAddress))
|
|
}
|
|
|
|
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 *testerV04x) expectDpdkInterfaceIP(result types.Result, ipAddress string) {
|
|
// check that the result was sane
|
|
res, err := types040.NewResultFromResult(result)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(len(res.Interfaces)).To(Equal(0))
|
|
Expect(len(res.IPs)).To(Equal(1))
|
|
Expect(res.IPs[0].Address.String()).To(Equal(ipAddress))
|
|
}
|
|
|
|
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,
|
|
},
|
|
}))
|
|
}
|
|
|
|
func (t *testerV03x) expectDpdkInterfaceIP(result types.Result, ipAddress string) {
|
|
// check that the result was sane
|
|
res, err := types040.NewResultFromResult(result)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(len(res.Interfaces)).To(Equal(0))
|
|
Expect(len(res.IPs)).To(Equal(1))
|
|
Expect(res.IPs[0].Address.String()).To(Equal(ipAddress))
|
|
}
|
|
|
|
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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
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 on a DPDK device with IPAM", ver), func() {
|
|
fs := &fakeFilesystem{
|
|
dirs: []string{
|
|
"sys/bus/pci/devices/0000:00:00.1",
|
|
"sys/bus/pci/drivers/vfio-pci",
|
|
},
|
|
symlinks: map[string]string{
|
|
"sys/bus/pci/devices/0000:00:00.1/driver": "../../../../bus/pci/drivers/vfio-pci",
|
|
},
|
|
}
|
|
defer fs.use()()
|
|
|
|
// 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"
|
|
}]
|
|
},
|
|
"pciBusID": %q
|
|
}`, ver, "0000:00:00.1")
|
|
args := &skel.CmdArgs{
|
|
ContainerID: "dummy",
|
|
IfName: cniName,
|
|
Netns: targetNS.Path(),
|
|
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.expectDpdkInterfaceIP(resI, targetIP)
|
|
|
|
// call CmdDel
|
|
_ = originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
err = testutils.CmdDelWithArgs(args, func() error {
|
|
return cmdDel(args)
|
|
})
|
|
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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
|
|
//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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
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 on a DPDK device with IPAM", ver), func() {
|
|
fs := &fakeFilesystem{
|
|
dirs: []string{
|
|
"sys/bus/pci/devices/0000:00:00.1",
|
|
"sys/bus/pci/drivers/vfio-pci",
|
|
},
|
|
symlinks: map[string]string{
|
|
"sys/bus/pci/devices/0000:00:00.1/driver": "../../../../bus/pci/drivers/vfio-pci",
|
|
},
|
|
}
|
|
defer fs.use()()
|
|
|
|
// 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"
|
|
}]
|
|
},
|
|
"pciBusID": %q
|
|
}`, ver, "0000:00:00.1")
|
|
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.expectDpdkInterfaceIP(resI, targetIP)
|
|
|
|
// 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
|
|
|
|
err = originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
// call CmdDel
|
|
_ = originalNS.Do(func(ns.NetNS) error {
|
|
defer GinkgoRecover()
|
|
err = testutils.CmdDelWithArgs(args, func() error {
|
|
return cmdDel(args)
|
|
})
|
|
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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
|
|
//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 and is up
|
|
_ = 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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
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 and is up
|
|
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))
|
|
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
|
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
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
type fakeFilesystem struct {
|
|
rootDir string
|
|
dirs []string
|
|
symlinks map[string]string
|
|
}
|
|
|
|
func (fs *fakeFilesystem) use() func() {
|
|
// create the new fake fs root dir in /tmp/sriov...
|
|
tmpDir, err := os.MkdirTemp("", "sriov")
|
|
if err != nil {
|
|
panic(fmt.Errorf("error creating fake root dir: %s", err.Error()))
|
|
}
|
|
fs.rootDir = tmpDir
|
|
|
|
for _, dir := range fs.dirs {
|
|
err := os.MkdirAll(path.Join(fs.rootDir, dir), 0755)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error creating fake directory: %s", err.Error()))
|
|
}
|
|
}
|
|
|
|
for link, target := range fs.symlinks {
|
|
err = os.Symlink(target, path.Join(fs.rootDir, link))
|
|
if err != nil {
|
|
panic(fmt.Errorf("error creating fake symlink: %s", err.Error()))
|
|
}
|
|
}
|
|
|
|
sysBusPCI = path.Join(fs.rootDir, "/sys/bus/pci/devices")
|
|
|
|
return func() {
|
|
// remove temporary fake fs
|
|
err := os.RemoveAll(fs.rootDir)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error tearing down fake filesystem: %s", err.Error()))
|
|
}
|
|
}
|
|
}
|