Dan Williams c8f341dff9 bridge: simplify version-based testcase code
Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-02-11 23:27:08 -06:00

1685 lines
45 KiB
Go

// Copyright 2015-2018 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"
"io/ioutil"
"net"
"os"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/vishvananda/netlink/nl"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
types040 "github.com/containernetworking/cni/pkg/types/040"
current "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/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
BRNAME = "bridge0"
BRNAMEVLAN = "bridge0.100"
IFNAME = "eth0"
)
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
BrName string `json:"bridge"`
IPAM *allocator.IPAMConfig `json:"ipam"`
//RuntimeConfig struct { // The capability arg
// IPRanges []RangeSet `json:"ipRanges,omitempty"`
//} `json:"runtimeConfig,omitempty"`
//Args *struct {
// A *IPAMArgs `json:"cni"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
// testCase defines the CNI network configuration and the expected
// bridge addresses for a test case.
type testCase struct {
cniVersion string // CNI Version
subnet string // Single subnet config: Subnet CIDR
gateway string // Single subnet config: Gateway
ranges []rangeInfo // Ranges list (multiple subnets config)
isGW bool
isLayer2 bool
expGWCIDRs []string // Expected gateway addresses in CIDR form
vlan int
ipMasq bool
}
// Range definition for each entry in the ranges list
type rangeInfo struct {
subnet string
gateway string
}
// netConf() creates a NetConf structure for a test case.
func (tc testCase) netConf() *NetConf {
return &NetConf{
NetConf: types.NetConf{
CNIVersion: tc.cniVersion,
Name: "testConfig",
Type: "bridge",
},
BrName: BRNAME,
IsGW: tc.isGW,
IPMasq: false,
MTU: 5000,
}
}
// Snippets for generating a JSON network configuration string.
const (
netConfStr = `
"cniVersion": "%s",
"name": "testConfig",
"type": "bridge",
"bridge": "%s"`
vlan = `,
"vlan": %d`
netDefault = `,
"isDefaultGateway": true`
ipamStartStr = `,
"ipam": {
"type": "host-local"`
ipamDataDirStr = `,
"dataDir": "%s"`
ipMasqConfStr = `,
"ipMasq": %t`
// Single subnet configuration (legacy)
subnetConfStr = `,
"subnet": "%s"`
gatewayConfStr = `,
"gateway": "%s"`
// Ranges (multiple subnets) configuration
rangesStartStr = `,
"ranges": [`
rangeSubnetConfStr = `
[{
"subnet": "%s"
}]`
rangeSubnetGWConfStr = `
[{
"subnet": "%s",
"gateway": "%s"
}]`
rangesEndStr = `
]`
ipamEndStr = `
}`
)
// netConfJSON() generates a JSON network configuration string
// for a test case.
func (tc testCase) netConfJSON(dataDir string) string {
conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME)
if tc.vlan != 0 {
conf += fmt.Sprintf(vlan, tc.vlan)
}
if tc.ipMasq {
conf += tc.ipMasqConfig()
}
if !tc.isLayer2 {
conf += netDefault
if tc.subnet != "" || tc.ranges != nil {
conf += ipamStartStr
if dataDir != "" {
conf += fmt.Sprintf(ipamDataDirStr, dataDir)
}
if tc.subnet != "" {
conf += tc.subnetConfig()
}
if tc.ranges != nil {
conf += tc.rangesConfig()
}
conf += ipamEndStr
}
} else {
conf += `,
"ipam": {}`
}
return "{" + conf + "\n}"
}
func (tc testCase) subnetConfig() string {
conf := fmt.Sprintf(subnetConfStr, tc.subnet)
if tc.gateway != "" {
conf += fmt.Sprintf(gatewayConfStr, tc.gateway)
}
return conf
}
func (tc testCase) ipMasqConfig() string {
conf := fmt.Sprintf(ipMasqConfStr, tc.ipMasq)
return conf
}
func (tc testCase) rangesConfig() string {
conf := rangesStartStr
for i, tcRange := range tc.ranges {
if i > 0 {
conf += ","
}
if tcRange.gateway != "" {
conf += fmt.Sprintf(rangeSubnetGWConfStr, tcRange.subnet, tcRange.gateway)
} else {
conf += fmt.Sprintf(rangeSubnetConfStr, tcRange.subnet)
}
}
return conf + rangesEndStr
}
var counter uint
// createCmdArgs generates network configuration and creates command
// arguments for a test case.
func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs {
conf := tc.netConfJSON(dataDir)
//defer func() { counter += 1 }()
return &skel.CmdArgs{
ContainerID: fmt.Sprintf("dummy-%d", counter),
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
}
// createCheckCmdArgs generates network configuration and creates command
// arguments for a Check test case.
func (tc testCase) createCheckCmdArgs(targetNS ns.NetNS, config *Net, dataDir string) *skel.CmdArgs {
conf, err := json.Marshal(config)
Expect(err).NotTo(HaveOccurred())
// TODO Don't we need to use the same counter as before?
//defer func() { counter += 1 }()
return &skel.CmdArgs{
ContainerID: fmt.Sprintf("dummy-%d", counter),
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
}
// expectedCIDRs determines the IPv4 and IPv6 CIDRs in which the resulting
// addresses are expected to be contained.
func (tc testCase) expectedCIDRs() ([]*net.IPNet, []*net.IPNet) {
var cidrsV4, cidrsV6 []*net.IPNet
appendSubnet := func(subnet string) {
ip, cidr, err := net.ParseCIDR(subnet)
Expect(err).NotTo(HaveOccurred())
if ipVersion(ip) == "4" {
cidrsV4 = append(cidrsV4, cidr)
} else {
cidrsV6 = append(cidrsV6, cidr)
}
}
if tc.subnet != "" {
appendSubnet(tc.subnet)
}
for _, r := range tc.ranges {
appendSubnet(r.subnet)
}
return cidrsV4, cidrsV6
}
// delBridgeAddrs() deletes addresses from the bridge
func delBridgeAddrs(testNS ns.NetNS) {
err := testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
br, err := netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
addrs, err := netlink.AddrList(br, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
err = netlink.AddrDel(br, &addr)
Expect(err).NotTo(HaveOccurred())
}
}
br, err = netlink.LinkByName(BRNAMEVLAN)
if err == nil {
addrs, err = netlink.AddrList(br, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
err = netlink.AddrDel(br, &addr)
Expect(err).NotTo(HaveOccurred())
}
}
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func delVlanAddrs(testNS ns.NetNS, vlan int) {
err := testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
vlanLink, err := netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, vlan))
Expect(err).NotTo(HaveOccurred())
addrs, err := netlink.AddrList(vlanLink, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
err = netlink.AddrDel(vlanLink, &addr)
Expect(err).NotTo(HaveOccurred())
}
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func ipVersion(ip net.IP) string {
if ip.To4() != nil {
return "4"
}
return "6"
}
func countIPAMIPs(path string) (int, error) {
count := 0
files, err := ioutil.ReadDir(path)
if err != nil {
return -1, err
}
for _, file := range files {
if file.IsDir() {
continue
}
if net.ParseIP(file.Name()) != nil {
count++
}
}
return count, nil
}
func checkVlan(vlanId int, bridgeVlanInfo []*nl.BridgeVlanInfo) bool {
for _, vlan := range bridgeVlanInfo {
if vlan.Vid == uint16(vlanId) {
return true
}
}
return false
}
type cmdAddDelTester interface {
cmdAddTest(tc testCase, dataDir string) (types.Result, error)
cmdCheckTest(tc testCase, conf *Net, dataDir string)
cmdDelTest(tc testCase, dataDir string)
}
type testerBase struct {
testNS ns.NetNS
targetNS ns.NetNS
args *skel.CmdArgs
vethName string
}
type testerV04x testerBase
type testerV03x testerBase
type testerV01xOr02x testerBase
func newTesterByVersion(version string, testNS, targetNS ns.NetNS) cmdAddDelTester {
switch {
case strings.HasPrefix(version, "0.4."):
return &testerV04x{
testNS: testNS,
targetNS: targetNS,
}
case strings.HasPrefix(version, "0.3."):
return &testerV03x{
testNS: testNS,
targetNS: targetNS,
}
default:
return &testerV01xOr02x{
testNS: testNS,
targetNS: targetNS,
}
}
}
func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
// Generate network config and command arguments
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
// Execute cmdADD on the plugin
var result *types040.Result
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
return cmdAdd(tester.args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
resultType, err := r.GetAsVersion(tc.cniVersion)
Expect(err).NotTo(HaveOccurred())
result = resultType.(*types040.Result)
Expect(len(result.Interfaces)).To(Equal(3))
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
Expect(result.Interfaces[0].Mac).To(HaveLen(17))
Expect(result.Interfaces[1].Name).To(HavePrefix("veth"))
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
Expect(result.Interfaces[2].Sandbox).To(Equal(tester.targetNS.Path()))
// Make sure bridge link exists
link, err := netlink.LinkByName(result.Interfaces[0].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
bridgeMAC := link.Attrs().HardwareAddr.String()
// Ensure bridge has expected gateway address(es)
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
for _, cidr := range tc.expGWCIDRs {
ip, subnet, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(ip) && aPrefix == subnetPrefix && aBits == subnetBits {
found = true
break
}
}
Expect(found).To(Equal(true))
}
// Check for the veth link in the main namespace
links, err := netlink.LinkList()
Expect(err).NotTo(HaveOccurred())
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
link, err = netlink.LinkByName(result.Interfaces[1].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
tester.vethName = result.Interfaces[1].Name
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = tester.targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
Expect(err).NotTo(HaveOccurred())
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
foundAddrs++
}
}
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
// Ensure the default route(s)
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound4, defaultRouteFound6 bool
for _, cidr := range tc.expGWCIDRs {
gwIP, _, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
var found *bool
if ipVersion(gwIP) == "4" {
found = &defaultRouteFound4
} else {
found = &defaultRouteFound6
}
if *found == true {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
}
Expect(*found).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
return result, nil
}
func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
// Generate network config and command arguments
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf, dataDir)
// Execute cmdCHECK on the plugin
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdCheckWithArgs(tester.args, func() error {
return cmdCheck(tester.args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = tester.targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
Expect(err).NotTo(HaveOccurred())
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
foundAddrs++
}
}
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
// Ensure the default route(s)
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound4, defaultRouteFound6 bool
for _, cidr := range tc.expGWCIDRs {
gwIP, _, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
var found *bool
if ipVersion(gwIP) == "4" {
found = &defaultRouteFound4
} else {
found = &defaultRouteFound6
}
if *found == true {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
}
Expect(*found).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func (tester *testerV04x) cmdDelTest(tc testCase, dataDir string) {
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(tester.args, func() error {
return cmdDel(tester.args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the host veth has been deleted
err = tester.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())
// Make sure the container veth has been deleted
err = tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(tester.vethName)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
// Generate network config and command arguments
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
// Execute cmdADD on the plugin
var result *types040.Result
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
return cmdAdd(tester.args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"interfaces\":")).Should(BeNumerically(">", 0))
resultType, err := r.GetAsVersion(tc.cniVersion)
Expect(err).NotTo(HaveOccurred())
result = resultType.(*types040.Result)
if !tc.isLayer2 && tc.vlan != 0 {
Expect(len(result.Interfaces)).To(Equal(4))
} else {
Expect(len(result.Interfaces)).To(Equal(3))
}
Expect(result.Interfaces[0].Name).To(Equal(BRNAME))
Expect(result.Interfaces[0].Mac).To(HaveLen(17))
Expect(result.Interfaces[1].Name).To(HavePrefix("veth"))
Expect(result.Interfaces[1].Mac).To(HaveLen(17))
Expect(result.Interfaces[2].Name).To(Equal(IFNAME))
Expect(result.Interfaces[2].Mac).To(HaveLen(17)) //mac is random
Expect(result.Interfaces[2].Sandbox).To(Equal(tester.targetNS.Path()))
// Make sure bridge link exists
link, err := netlink.LinkByName(result.Interfaces[0].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
Expect(link.Attrs().HardwareAddr.String()).To(Equal(result.Interfaces[0].Mac))
bridgeMAC := link.Attrs().HardwareAddr.String()
var vlanLink netlink.Link
if !tc.isLayer2 && tc.vlan != 0 {
// Make sure vlan link exists
vlanLink, err = netlink.LinkByName(fmt.Sprintf("%s.%d", BRNAME, tc.vlan))
Expect(err).NotTo(HaveOccurred())
Expect(vlanLink.Attrs().Name).To(Equal(fmt.Sprintf("%s.%d", BRNAME, tc.vlan)))
Expect(vlanLink).To(BeAssignableToTypeOf(&netlink.Veth{}))
// Check the bridge dot vlan interface have the vlan tag
peerLink, err := netlink.LinkByIndex(vlanLink.Attrs().Index - 1)
Expect(err).NotTo(HaveOccurred())
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(peerLink.Attrs().Index)]
Expect(isExist).To(BeTrue())
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
}
// Check the bridge vlan filtering equals true
if tc.vlan != 0 {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(true))
} else {
Expect(*link.(*netlink.Bridge).VlanFiltering).To(Equal(false))
}
// Ensure bridge has expected gateway address(es)
var addrs []netlink.Addr
if tc.vlan == 0 {
addrs, err = netlink.AddrList(link, netlink.FAMILY_ALL)
} else {
addrs, err = netlink.AddrList(vlanLink, netlink.FAMILY_ALL)
}
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
for _, cidr := range tc.expGWCIDRs {
ip, subnet, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(ip) && aPrefix == subnetPrefix && aBits == subnetBits {
found = true
break
}
}
Expect(found).To(Equal(true))
}
// Check for the veth link in the main namespace
links, err := netlink.LinkList()
Expect(err).NotTo(HaveOccurred())
if !tc.isLayer2 && tc.vlan != 0 {
Expect(len(links)).To(Equal(5)) // Bridge, Bridge vlan veth, veth, and loopback
} else {
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
}
link, err = netlink.LinkByName(result.Interfaces[1].Name)
Expect(err).NotTo(HaveOccurred())
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
tester.vethName = result.Interfaces[1].Name
// check vlan exist on the veth interface
if tc.vlan != 0 {
interfaceMap, err := netlink.BridgeVlanList()
Expect(err).NotTo(HaveOccurred())
vlans, isExist := interfaceMap[int32(link.Attrs().Index)]
Expect(isExist).To(BeTrue())
Expect(checkVlan(tc.vlan, vlans)).To(BeTrue())
}
// Check that the bridge has a different mac from the veth
// If not, it means the bridge has an unstable mac and will change
// as ifs are added and removed
// this check is not relevant for a layer 2 bridge
if !tc.isLayer2 && tc.vlan == 0 {
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(bridgeMAC))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = tester.targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(err).NotTo(HaveOccurred())
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
for _, addr := range addrs {
if !addr.IP.IsLinkLocalUnicast() {
foundAddrs++
}
}
Expect(foundAddrs).To(Equal(len(expCIDRsV6)))
// Ensure the default route(s)
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound4, defaultRouteFound6 bool
for _, cidr := range tc.expGWCIDRs {
gwIP, _, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
var found *bool
if ipVersion(gwIP) == "4" {
found = &defaultRouteFound4
} else {
found = &defaultRouteFound6
}
if *found == true {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
}
Expect(*found).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
return result, nil
}
func (tester *testerV03x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
return
}
func (tester *testerV03x) cmdDelTest(tc testCase, dataDir string) {
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(tester.args, func() error {
return cmdDel(tester.args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the host veth has been deleted
err = tester.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())
// Make sure the container veth has been deleted
err = tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(tester.vethName)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Result, error) {
// Generate network config and calculate gateway addresses
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
// Execute cmdADD on the plugin
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
return cmdAdd(tester.args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"ip\":")).Should(BeNumerically(">", 0))
// We expect a version 0.1.0 result
_, err = r.GetAsVersion(tc.cniVersion)
Expect(err).NotTo(HaveOccurred())
// Make sure bridge link exists
link, err := netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Bridge{}))
// Ensure bridge has expected gateway address(es)
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
for _, cidr := range tc.expGWCIDRs {
ip, subnet, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(ip) && aPrefix == subnetPrefix && aBits == subnetBits {
found = true
break
}
}
Expect(found).To(Equal(true))
}
// Check for the veth link in the main namespace; can't
// check the for the specific link since version 0.1.0
// doesn't report interfaces
links, err := netlink.LinkList()
Expect(err).NotTo(HaveOccurred())
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
err = tester.targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) // Link local address is automatic
// Ensure the default route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound bool
for _, cidr := range tc.expGWCIDRs {
gwIP, _, err := net.ParseCIDR(cidr)
Expect(err).NotTo(HaveOccurred())
for _, route := range routes {
defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
if defaultRouteFound {
break
}
}
Expect(defaultRouteFound).To(Equal(true))
}
return nil
})
Expect(err).NotTo(HaveOccurred())
return nil, nil
}
func (tester *testerV01xOr02x) cmdCheckTest(tc testCase, conf *Net, dataDir string) {
return
}
func (tester *testerV01xOr02x) cmdDelTest(tc testCase, dataDir string) {
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(tester.args, func() error {
return cmdDel(tester.args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure the container veth has been deleted; cannot check
// host veth as version 0.1.0 can't report its name
err = tester.testNS.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())
}
func cmdAddDelTest(testNS ns.NetNS, tc testCase, dataDir string) {
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNS.Close()
tester := newTesterByVersion(tc.cniVersion, testNS, targetNS)
// Test IP allocation
result, err := tester.cmdAddTest(tc, dataDir)
Expect(err).NotTo(HaveOccurred())
greater, err := version.GreaterThanOrEqualTo(tc.cniVersion, "0.3.0")
Expect(err).NotTo(HaveOccurred())
if greater {
Expect(result).NotTo(BeNil())
} else {
Expect(result).To(BeNil())
}
// Test IP Release
tester.cmdDelTest(tc, dataDir)
// Clean up bridge addresses for next test case
delBridgeAddrs(testNS)
}
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
}
func cmdAddDelCheckTest(testNS ns.NetNS, tc testCase, dataDir string) {
Expect(tc.cniVersion).To(Equal("0.4.0"))
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNS.Close()
tester := newTesterByVersion(tc.cniVersion, testNS, targetNS)
// Test IP allocation
prevResult, err := tester.cmdAddTest(tc, dataDir)
Expect(err).NotTo(HaveOccurred())
Expect(prevResult).NotTo(BeNil())
confString := tc.netConfJSON(dataDir)
conf := &Net{}
err = json.Unmarshal([]byte(confString), &conf)
Expect(err).NotTo(HaveOccurred())
conf.IPAM, _, err = allocator.LoadIPAMConfig([]byte(confString), "")
Expect(err).NotTo(HaveOccurred())
newConf, err := buildOneConfig("testConfig", tc.cniVersion, conf, prevResult)
Expect(err).NotTo(HaveOccurred())
// Test CHECK
tester.cmdCheckTest(tc, newConf, dataDir)
// Test IP Release
tester.cmdDelTest(tc, dataDir)
// Clean up bridge addresses for next test case
delBridgeAddrs(testNS)
if tc.vlan != 0 && !tc.isLayer2 {
delVlanAddrs(testNS, tc.vlan)
}
}
var _ = Describe("bridge Operations", func() {
var originalNS ns.NetNS
var dataDir string
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
dataDir, err = ioutil.TempDir("", "bridge_test")
Expect(err).NotTo(HaveOccurred())
// Do not emulate an error, each test will set this if needed
debugPostIPAMError = nil
})
AfterEach(func() {
Expect(os.RemoveAll(dataDir)).To(Succeed())
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates a bridge", func() {
conf := testCase{cniVersion: "0.3.1"}.netConf()
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
bridge, _, err := setupBridge(conf)
Expect(err).NotTo(HaveOccurred())
Expect(bridge.Attrs().Name).To(Equal(BRNAME))
// Double check that the link was added
link, err := netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link.Attrs().Promisc).To(Equal(0))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("handles an existing bridge", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: BRNAME,
},
})
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
ifindex := link.Attrs().Index
tc := testCase{cniVersion: "0.3.1", isGW: false}
conf := tc.netConf()
bridge, _, err := setupBridge(conf)
Expect(err).NotTo(HaveOccurred())
Expect(bridge.Attrs().Name).To(Equal(BRNAME))
Expect(bridge.Attrs().Index).To(Equal(ifindex))
// Double check that the link has the same ifindex
link, err = netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
Expect(link.Attrs().Index).To(Equal(ifindex))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.0 config", func() {
testCases := []testCase{
{
// IPv4 only
subnet: "10.1.2.0/24",
expGWCIDRs: []string{"10.1.2.1/24"},
},
{
// IPv6 only
subnet: "2001:db8::0/64",
expGWCIDRs: []string{"2001:db8::1/64"},
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
},
{
// 3 Subnets (1 IPv4 and 2 IPv6 subnets)
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
{subnet: "2001:db8::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
"2001:db8::1/64",
},
},
}
for _, tc := range testCases {
tc.cniVersion = "0.3.0"
cmdAddDelTest(originalNS, tc, dataDir)
}
})
It("configures and deconfigures a l2 bridge and veth with ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.0", isLayer2: true}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a l2 bridge and veth with ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.1", isLayer2: true}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.0", isLayer2: true, vlan: 100}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a l2 bridge with vlan id 100 using ADD/DEL for 0.3.1 config", func() {
tc := testCase{cniVersion: "0.3.1", isLayer2: true, vlan: 100}
cmdAddDelTest(originalNS, tc, dataDir)
})
It("configures and deconfigures a bridge, veth with default route and vlanID 100 with ADD/DEL for 0.3.0 config", func() {
testCases := []testCase{
{
// IPv4 only
subnet: "10.1.2.0/24",
expGWCIDRs: []string{"10.1.2.1/24"},
vlan: 100,
},
{
// IPv6 only
subnet: "2001:db8::0/64",
expGWCIDRs: []string{"2001:db8::1/64"},
vlan: 100,
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
vlan: 100,
},
{
// 3 Subnets (1 IPv4 and 2 IPv6 subnets)
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
{subnet: "2001:db8::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
"2001:db8::1/64",
},
vlan: 100,
},
}
for _, tc := range testCases {
tc.cniVersion = "0.3.0"
cmdAddDelTest(originalNS, tc, dataDir)
}
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.3.1 config", func() {
testCases := []testCase{
{
// IPv4 only
subnet: "10.1.2.0/24",
expGWCIDRs: []string{"10.1.2.1/24"},
},
{
// IPv6 only
subnet: "2001:db8::0/64",
expGWCIDRs: []string{"2001:db8::1/64"},
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
},
}
for _, tc := range testCases {
tc.cniVersion = "0.3.1"
cmdAddDelTest(originalNS, tc, dataDir)
}
})
It("deconfigures an unconfigured bridge with DEL", func() {
tc := testCase{
cniVersion: "0.3.0",
subnet: "10.1.2.0/24",
expGWCIDRs: []string{"10.1.2.1/24"},
}
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNS.Close()
tester := testerV03x{
testNS: originalNS,
targetNS: targetNS,
}
tester.args = tc.createCmdArgs(targetNS, dataDir)
// Execute cmdDEL on the plugin, expect no errors
tester.cmdDelTest(tc, dataDir)
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() {
testCases := []testCase{
{
// IPv4 only
subnet: "10.1.2.0/24",
expGWCIDRs: []string{"10.1.2.1/24"},
},
{
// IPv6 only
subnet: "2001:db8::0/64",
expGWCIDRs: []string{"2001:db8::1/64"},
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
},
}
for _, tc := range testCases {
tc.cniVersion = "0.1.0"
cmdAddDelTest(originalNS, tc, dataDir)
}
})
It("ensure bridge address", func() {
conf := testCase{cniVersion: "0.3.1", isGW: true}.netConf()
testCases := []struct {
gwCIDRFirst string
gwCIDRSecond string
}{
{
// IPv4
gwCIDRFirst: "10.0.0.1/8",
gwCIDRSecond: "10.1.2.3/16",
},
{
// IPv6, overlapping subnets
gwCIDRFirst: "2001:db8:1::1/48",
gwCIDRSecond: "2001:db8:1:2::1/64",
},
{
// IPv6, non-overlapping subnets
gwCIDRFirst: "2001:db8:1:2::1/64",
gwCIDRSecond: "fd00:1234::1/64",
},
}
for _, tc := range testCases {
gwIP, gwSubnet, err := net.ParseCIDR(tc.gwCIDRFirst)
Expect(err).NotTo(HaveOccurred())
gwnFirst := net.IPNet{IP: gwIP, Mask: gwSubnet.Mask}
gwIP, gwSubnet, err = net.ParseCIDR(tc.gwCIDRSecond)
Expect(err).NotTo(HaveOccurred())
gwnSecond := net.IPNet{IP: gwIP, Mask: gwSubnet.Mask}
var family, expNumAddrs int
switch {
case gwIP.To4() != nil:
family = netlink.FAMILY_V4
expNumAddrs = 1
default:
family = netlink.FAMILY_V6
// Expect configured gw address plus link local
expNumAddrs = 2
}
subnetsOverlap := gwnFirst.Contains(gwnSecond.IP) ||
gwnSecond.Contains(gwnFirst.IP)
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Create the bridge
bridge, _, err := setupBridge(conf)
Expect(err).NotTo(HaveOccurred())
// Function to check IP address(es) on bridge
checkBridgeIPs := func(cidr0, cidr1 string) {
addrs, err := netlink.AddrList(bridge, family)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(expNumAddrs))
addr := addrs[0].IPNet.String()
Expect(addr).To(Equal(cidr0))
if cidr1 != "" {
addr = addrs[1].IPNet.String()
Expect(addr).To(Equal(cidr1))
}
}
// Check if ForceAddress has default value
Expect(conf.ForceAddress).To(Equal(false))
// Set first address on bridge
err = ensureAddr(bridge, family, &gwnFirst, conf.ForceAddress)
Expect(err).NotTo(HaveOccurred())
checkBridgeIPs(tc.gwCIDRFirst, "")
// Attempt to set the second address on the bridge
// with ForceAddress set to false.
err = ensureAddr(bridge, family, &gwnSecond, false)
if family == netlink.FAMILY_V4 || subnetsOverlap {
// IPv4 or overlapping IPv6 subnets:
// Expect an error, and address should remain the same
Expect(err).To(HaveOccurred())
checkBridgeIPs(tc.gwCIDRFirst, "")
} else {
// Non-overlapping IPv6 subnets:
// There should be 2 addresses (in addition to link local)
Expect(err).NotTo(HaveOccurred())
expNumAddrs++
checkBridgeIPs(tc.gwCIDRSecond, tc.gwCIDRFirst)
}
// Set the second address on the bridge
// with ForceAddress set to true.
err = ensureAddr(bridge, family, &gwnSecond, true)
Expect(err).NotTo(HaveOccurred())
if family == netlink.FAMILY_V4 || subnetsOverlap {
// IPv4 or overlapping IPv6 subnets:
// IP address should be reconfigured.
checkBridgeIPs(tc.gwCIDRSecond, "")
} else {
// Non-overlapping IPv6 subnets:
// There should be 2 addresses (in addition to link local)
checkBridgeIPs(tc.gwCIDRSecond, tc.gwCIDRFirst)
}
return nil
})
Expect(err).NotTo(HaveOccurred())
// Clean up bridge addresses for next test case
delBridgeAddrs(originalNS)
}
})
It("ensure promiscuous mode on bridge", func() {
const IFNAME = "bridge0"
const EXPECTED_IP = "10.0.0.0/8"
const CHANGED_EXPECTED_IP = "10.1.2.3/16"
conf := &NetConf{
NetConf: types.NetConf{
CNIVersion: "0.3.1",
Name: "testConfig",
Type: "bridge",
},
BrName: IFNAME,
IsGW: true,
IPMasq: false,
HairpinMode: false,
PromiscMode: true,
MTU: 5000,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := setupBridge(conf)
Expect(err).NotTo(HaveOccurred())
// Check if ForceAddress has default value
Expect(conf.ForceAddress).To(Equal(false))
//Check if promiscuous mode is set correctly
link, err := netlink.LinkByName("bridge0")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Promisc).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("creates a bridge with a stable MAC addresses", func() {
testCases := []testCase{
{
subnet: "10.1.2.0/24",
},
{
subnet: "2001:db8:42::/64",
},
}
for _, tc := range testCases {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc.cniVersion = "0.3.1"
_, _, err := setupBridge(tc.netConf())
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
origMac := link.Attrs().HardwareAddr
cmdAddDelTest(originalNS, tc, dataDir)
link, err = netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origMac))
return nil
})
Expect(err).NotTo(HaveOccurred())
}
})
It("checks ip release in case of error", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: "0.3.1",
subnet: "10.1.2.0/24",
}
_, _, err := setupBridge(tc.netConf())
Expect(err).NotTo(HaveOccurred())
args := tc.createCmdArgs(originalNS, dataDir)
// get number of allocated IPs before asking for a new one
before, err := countIPAMIPs(dataDir)
Expect(err).NotTo(HaveOccurred())
debugPostIPAMError = fmt.Errorf("debugPostIPAMError")
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(MatchError("debugPostIPAMError"))
// get number of allocated IPs after failure
after, err := countIPAMIPs(dataDir)
Expect(err).NotTo(HaveOccurred())
Expect(before).To(Equal(after))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL/CHECK for 0.4.0 config", func() {
testCases := []testCase{
{
// IPv4 only
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
expGWCIDRs: []string{"10.1.2.1/24"},
},
{
// IPv6 only
ranges: []rangeInfo{{
subnet: "2001:db8::0/64",
}},
expGWCIDRs: []string{"2001:db8::1/64"},
},
{
// Dual-Stack
ranges: []rangeInfo{
{subnet: "192.168.0.0/24"},
{subnet: "fd00::0/64"},
},
expGWCIDRs: []string{
"192.168.0.1/24",
"fd00::1/64",
},
},
}
for _, tc := range testCases {
tc.cniVersion = "0.4.0"
cmdAddDelCheckTest(originalNS, tc, dataDir)
}
})
It("configures a bridge and ipMasq rules for 0.4.0 config", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
cniVersion: "0.4.0",
}
args := tc.createCmdArgs(originalNS, dataDir)
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).Should(HaveLen(1))
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
rules, err := ipt.List("nat", "POSTROUTING")
Expect(err).NotTo(HaveOccurred())
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("check vlan id when loading net conf", func() {
tests := []struct {
tc testCase
err error
}{
{
tc: testCase{
cniVersion: "0.4.0",
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 0,
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: -100,
},
err: fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)"),
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 5000,
},
err: fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)"),
},
}
for _, test := range tests {
_, _, err := loadNetConf([]byte(test.tc.netConfJSON("")))
if test.err == nil {
Expect(err).To(BeNil())
} else {
Expect(err).To(Equal(test.err))
}
}
})
})