Add check support for: bridge, ipvlan, macvlan, p2p, vlan and host-device main plugins

host-local and static ipam plugins
  tuning, bandwidth and portmap meta plugins

  Utility functions created for common PrevResult checking

  Fix windows build
This commit is contained in:
Michael Cambria 2018-12-06 15:42:37 -05:00
parent 82a0651d0a
commit 74a2596573
28 changed files with 3759 additions and 167 deletions

120
pkg/ip/utils_linux.go Normal file
View File

@ -0,0 +1,120 @@
// +build linux
// Copyright 2016 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 ip
import (
"fmt"
"net"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/vishvananda/netlink"
)
func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error {
// Ensure ips
for _, ips := range resultIPs {
ourAddr := netlink.Addr{IPNet: &ips.Address}
match := false
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("Cannot find container link %v", ifName)
}
addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return fmt.Errorf("Cannot obtain List of IP Addresses")
}
for _, addr := range addrList {
if addr.Equal(ourAddr) {
match = true
break
}
}
if match == false {
return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName)
}
// Convert the host/prefixlen to just prefix for route lookup.
_, ourPrefix, err := net.ParseCIDR(ourAddr.String())
findGwy := &netlink.Route{Dst: ourPrefix}
routeFilter := netlink.RT_FILTER_DST
var family int
switch {
case ips.Version == "4":
family = netlink.FAMILY_V4
case ips.Version == "6":
family = netlink.FAMILY_V6
default:
return fmt.Errorf("Invalid IP Version %v for interface %v", ips.Version, ifName)
}
gwy, err := netlink.RouteListFiltered(family, findGwy, routeFilter)
if err != nil {
return fmt.Errorf("Error %v trying to find Gateway %v for interface %v", err, ips.Gateway, ifName)
}
if gwy == nil {
return fmt.Errorf("Failed to find Gateway %v for interface %v", ips.Gateway, ifName)
}
}
return nil
}
func ValidateExpectedRoute(resultRoutes []*types.Route) error {
// Ensure that each static route in prevResults is found in the routing table
for _, route := range resultRoutes {
find := &netlink.Route{Dst: &route.Dst, Gw: route.GW}
routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW
var family int
switch {
case route.Dst.IP.To4() != nil:
family = netlink.FAMILY_V4
// Default route needs Dst set to nil
if route.Dst.String() == "0.0.0.0/0" {
find = &netlink.Route{Dst: nil, Gw: route.GW}
routeFilter = netlink.RT_FILTER_DST
}
case len(route.Dst.IP) == net.IPv6len:
family = netlink.FAMILY_V6
// Default route needs Dst set to nil
if route.Dst.String() == "::/0" {
find = &netlink.Route{Dst: nil, Gw: route.GW}
routeFilter = netlink.RT_FILTER_DST
}
default:
return fmt.Errorf("Invalid static route found %v", route)
}
wasFound, err := netlink.RouteListFiltered(family, find, routeFilter)
if err != nil {
return fmt.Errorf("Expected Route %v not route table lookup error %v", route, err)
}
if wasFound == nil {
return fmt.Errorf("Expected Route %v not found in routing table", route)
}
}
return nil
}

View File

@ -24,6 +24,10 @@ func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
return invoke.DelegateAdd(context.TODO(), plugin, netconf, nil) return invoke.DelegateAdd(context.TODO(), plugin, netconf, nil)
} }
func ExecCheck(plugin string, netconf []byte) error {
return invoke.DelegateCheck(context.TODO(), plugin, netconf, nil)
}
func ExecDel(plugin string, netconf []byte) error { func ExecDel(plugin string, netconf []byte) error {
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil) return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
} }

View File

@ -81,6 +81,21 @@ func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, e
return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f) return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
} }
func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error {
os.Setenv("CNI_COMMAND", "CHECK")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
defer envCleanup()
return f()
}
func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error {
return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
}
func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error { func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
os.Setenv("CNI_COMMAND", "DEL") os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_PATH", os.Getenv("PATH")) os.Setenv("CNI_PATH", os.Getenv("PATH"))

View File

@ -52,7 +52,7 @@ func main() {
} }
} else { } else {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
} }
@ -80,9 +80,23 @@ func cmdDel(args *skel.CmdArgs) error {
return nil return nil
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement // TODO: implement
return fmt.Errorf("not implemented") //return fmt.Errorf("not implemented")
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{}
//confVersion, err := versionDecoder.Decode(args.StdinData)
_, err := versionDecoder.Decode(args.StdinData)
if err != nil {
return err
}
result := &current.Result{}
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
return err
}
return nil
} }
type SocketPathConf struct { type SocketPathConf struct {

View File

@ -98,6 +98,43 @@ func (s *Store) Release(ip net.IP) error {
return os.Remove(GetEscapedPath(s.dataDir, ip.String())) return os.Remove(GetEscapedPath(s.dataDir, ip.String()))
} }
func (s *Store) FindByKey(id string, ifname string, match string) (bool, error) {
found := false
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil
}
if strings.TrimSpace(string(data)) == match {
found = true
}
return nil
})
return found, err
}
func (s *Store) FindByID(id string, ifname string) bool {
s.Lock()
defer s.Unlock()
found := false
match := strings.TrimSpace(id) + LineBreak + ifname
found, err := s.FindByKey(id, ifname, match)
// Match anything created by this id
if !found && err == nil {
match := strings.TrimSpace(id)
found, err = s.FindByKey(id, ifname, match)
}
return found
}
func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) { func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) {
found := false found := false
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"strings" "strings"
@ -30,12 +31,38 @@ import (
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
// TODO: implement n := &types.NetConf{}
return fmt.Errorf("not implemented") if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
return n, n.CNIVersion, nil
}
func cmdCheck(args *skel.CmdArgs) error {
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
if err != nil {
return err
}
// Look to see if there is at least one IP address allocated to the container
// in the data dir, irrespective of what that address actually is
store, err := disk.New(ipamConf.Name, ipamConf.DataDir)
if err != nil {
return err
}
defer store.Close()
containerIpFound := store.FindByID(args.ContainerID, args.IfName)
if containerIpFound == false {
return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID)
}
return nil
} }
func cmdAdd(args *skel.CmdArgs) error { func cmdAdd(args *skel.CmdArgs) error {

View File

@ -59,12 +59,59 @@ type Address struct {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
// TODO: implement n := &types.NetConf{}
return fmt.Errorf("not implemented") if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
return n, n.CNIVersion, nil
}
func cmdCheck(args *skel.CmdArgs) error {
ipamConf, _, err := LoadIPAMConfig(args.StdinData, args.Args)
if err != nil {
return err
}
// Get PrevResult from stdin... store in RawPrevResult
n, _, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
// Parse previous result.
if n.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(n); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
// Each configured IP should be found in result.IPs
for _, rangeset := range ipamConf.Addresses {
for _, ips := range result.IPs {
// Ensure values are what we expect
if rangeset.Address.IP.Equal(ips.Address.IP) {
if rangeset.Gateway == nil {
break
} else if rangeset.Gateway.Equal(ips.Gateway) {
break
}
return fmt.Errorf("static: Failed to match addr %v on interface %v", ips.Address.IP, args.IfName)
}
}
}
return nil
} }
// canonicalizeIP makes sure a provided ip is in standard form // canonicalizeIP makes sure a provided ip is in standard form

View File

@ -537,10 +537,269 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { type cniBridgeIf struct {
// TODO: implement Name string
return fmt.Errorf("not implemented") ifIndex int
peerIndex int
masterIndex int
found bool
}
func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, netlink.Link, error) {
ifFound := cniBridgeIf{found: false}
if intf.Name == "" {
return ifFound, nil, fmt.Errorf("Interface name missing ")
}
link, err := netlink.LinkByName(intf.Name)
if err != nil {
return ifFound, nil, fmt.Errorf("Interface name %s not found", intf.Name)
}
if expectInSb {
if intf.Sandbox == "" {
return ifFound, nil, fmt.Errorf("Interface %s is expected to be in a sandbox", intf.Name)
}
} else {
if intf.Sandbox != "" {
return ifFound, nil, fmt.Errorf("Interface %s should not be in sandbox", intf.Name)
}
}
return ifFound, link, err
}
func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, error) {
brFound, link, err := validateInterface(intf, false)
if err != nil {
return brFound, err
}
_, isBridge := link.(*netlink.Bridge)
if !isBridge {
return brFound, fmt.Errorf("Interface %s does not have link type of bridge", intf.Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return brFound, fmt.Errorf("Bridge interface %s Mac doesn't match: %s", intf.Name, intf.Mac)
}
}
linkPromisc := link.Attrs().Promisc != 0
if linkPromisc != n.PromiscMode {
return brFound, fmt.Errorf("Bridge interface %s configured Promisc Mode %v doesn't match current state: %v ",
intf.Name, n.PromiscMode, linkPromisc)
}
brFound.found = true
brFound.Name = link.Attrs().Name
brFound.ifIndex = link.Attrs().Index
brFound.masterIndex = link.Attrs().MasterIndex
return brFound, nil
}
func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf cniBridgeIf) (cniBridgeIf, error) {
vethFound, link, err := validateInterface(*intf, false)
if err != nil {
return vethFound, err
}
_, isVeth := link.(*netlink.Veth)
if !isVeth {
// just skip it, it's not what CNI created
return vethFound, nil
}
_, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name)
if err != nil {
return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name)
}
vethFound.ifIndex = link.Attrs().Index
vethFound.masterIndex = link.Attrs().MasterIndex
if vethFound.ifIndex != contIf.peerIndex {
return vethFound, nil
}
if contIf.ifIndex != vethFound.peerIndex {
return vethFound, nil
}
if vethFound.masterIndex != brIf.ifIndex {
return vethFound, nil
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return vethFound, fmt.Errorf("Interface %s Mac doesn't match: %s not found", intf.Name, intf.Mac)
}
}
vethFound.found = true
vethFound.Name = link.Attrs().Name
return vethFound, nil
}
func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error) {
vethFound, link, err := validateInterface(intf, true)
if err != nil {
return vethFound, err
}
_, isVeth := link.(*netlink.Veth)
if !isVeth {
return vethFound, fmt.Errorf("Error: Container interface %s not of type veth", link.Attrs().Name)
}
_, vethFound.peerIndex, err = ip.GetVethPeerIfindex(link.Attrs().Name)
if err != nil {
return vethFound, fmt.Errorf("Unable to obtain veth peer index for veth %s", link.Attrs().Name)
}
vethFound.ifIndex = link.Attrs().Index
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return vethFound, fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
vethFound.found = true
vethFound.Name = link.Attrs().Name
return vethFound, nil
}
func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
// Parse previous result.
if n.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&n.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
var errLink error
var contCNI, vethCNI cniBridgeIf
var brMap, contMap current.Interface
// Find interfaces for names whe know, CNI Bridge and container
for _, intf := range result.Interfaces {
if n.BrName == intf.Name {
brMap = *intf
continue
} else if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
brCNI, err := validateCniBrInterface(brMap, n)
if err != nil {
return err
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
// Check interface against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
contCNI, errLink = validateCniContainerInterface(contMap)
if errLink != nil {
return errLink
}
return nil
}); err != nil {
return err
}
// Now look for veth that is peer with container interface.
// Anything else wasn't created by CNI, skip it
for _, intf := range result.Interfaces {
// Skip this result if name is the same as cni bridge
// It's either the cni bridge we dealt with above, or something with the
// same name in a different namespace. We just skip since it's not ours
if brMap.Name == intf.Name {
continue
}
// same here for container name
if contMap.Name == intf.Name {
continue
}
vethCNI, errLink = validateCniVethInterface(intf, brCNI, contCNI)
if errLink != nil {
return errLink
}
if vethCNI.found {
// veth with container interface as peer and bridge as master found
break
}
}
if !brCNI.found {
return fmt.Errorf("CNI created bridge %s in host namespace was not found", n.BrName)
}
if !contCNI.found {
return fmt.Errorf("CNI created interface in container %s not found", args.IfName)
}
if !vethCNI.found {
return fmt.Errorf("CNI veth created for bridge %s was not found", n.BrName)
}
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
} }

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@ -30,6 +31,7 @@ import (
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -39,6 +41,22 @@ const (
IFNAME = "eth0" 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 // testCase defines the CNI network configuration and the expected
// bridge addresses for a test case. // bridge addresses for a test case.
type testCase struct { type testCase struct {
@ -171,7 +189,24 @@ var counter uint
// arguments for a test case. // arguments for a test case.
func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs { func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs {
conf := tc.netConfJSON(dataDir) conf := tc.netConfJSON(dataDir)
defer func() { counter += 1 }() //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{ return &skel.CmdArgs{
ContainerID: fmt.Sprintf("dummy-%d", counter), ContainerID: fmt.Sprintf("dummy-%d", counter),
Netns: targetNS.Path(), Netns: targetNS.Path(),
@ -250,12 +285,15 @@ func countIPAMIPs(path string) (int, error) {
type cmdAddDelTester interface { type cmdAddDelTester interface {
setNS(testNS ns.NetNS, targetNS ns.NetNS) setNS(testNS ns.NetNS, targetNS ns.NetNS)
cmdAddTest(tc testCase, dataDir string) cmdAddTest(tc testCase, dataDir string) (*current.Result, error)
cmdDelTest(tc testCase) cmdCheckTest(tc testCase, conf *Net, dataDir string)
cmdDelTest(tc testCase, dataDir string)
} }
func testerByVersion(version string) cmdAddDelTester { func testerByVersion(version string) cmdAddDelTester {
switch { switch {
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
case strings.HasPrefix(version, "0.3."): case strings.HasPrefix(version, "0.3."):
return &testerV03x{} return &testerV03x{}
default: default:
@ -263,6 +301,263 @@ func testerByVersion(version string) cmdAddDelTester {
} }
} }
type testerV04x struct {
testNS ns.NetNS
targetNS ns.NetNS
args *skel.CmdArgs
vethName string
}
func (tester *testerV04x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
tester.testNS = testNS
tester.targetNS = targetNS
}
func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
// Generate network config and command arguments
tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
// Execute cmdADD on the plugin
var result *current.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))
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
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())
}
type testerV03x struct { type testerV03x struct {
testNS ns.NetNS testNS ns.NetNS
targetNS ns.NetNS targetNS ns.NetNS
@ -275,7 +570,7 @@ func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
tester.targetNS = targetNS tester.targetNS = targetNS
} }
func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) { func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
// Generate network config and command arguments // Generate network config and command arguments
tester.args = tc.createCmdArgs(tester.targetNS, dataDir) tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
@ -409,9 +704,14 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) {
return nil return nil
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
return result, nil
} }
func (tester *testerV03x) cmdDelTest(tc testCase) { 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 { err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
@ -458,7 +758,7 @@ func (tester *testerV01xOr02x) setNS(testNS ns.NetNS, targetNS ns.NetNS) {
tester.targetNS = targetNS tester.targetNS = targetNS
} }
func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) { func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (*current.Result, error) {
// Generate network config and calculate gateway addresses // Generate network config and calculate gateway addresses
tester.args = tc.createCmdArgs(tester.targetNS, dataDir) tester.args = tc.createCmdArgs(tester.targetNS, dataDir)
@ -549,9 +849,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) {
return nil return nil
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
return nil, nil
} }
func (tester *testerV01xOr02x) cmdDelTest(tc testCase) { 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 { err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
@ -586,10 +891,98 @@ func cmdAddDelTest(testNS ns.NetNS, tc testCase, dataDir string) {
tester.setNS(testNS, targetNS) tester.setNS(testNS, targetNS)
// Test IP allocation // Test IP allocation
tester.cmdAddTest(tc, dataDir) result, err := tester.cmdAddTest(tc, dataDir)
Expect(err).NotTo(HaveOccurred())
if strings.HasPrefix(tc.cniVersion, "0.3.") {
Expect(result).NotTo(BeNil())
} else {
Expect(result).To(BeNil())
}
// Test IP Release // Test IP Release
tester.cmdDelTest(tc) 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"))
// Get a Add/Del tester based on test case version
tester := testerByVersion(tc.cniVersion)
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNS.Close()
tester.setNS(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 // Clean up bridge addresses for next test case
delBridgeAddrs(testNS) delBridgeAddrs(testNS)
@ -767,7 +1160,7 @@ var _ = Describe("bridge Operations", func() {
tester.args = tc.createCmdArgs(targetNS, dataDir) tester.args = tc.createCmdArgs(targetNS, dataDir)
// Execute cmdDEL on the plugin, expect no errors // Execute cmdDEL on the plugin, expect no errors
tester.cmdDelTest(tc) tester.cmdDelTest(tc, dataDir)
}) })
It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() { It("configures and deconfigures a bridge and veth with default route with ADD/DEL for 0.1.0 config", func() {
@ -1007,4 +1400,38 @@ var _ = Describe("bridge Operations", func() {
}) })
Expect(err).NotTo(HaveOccurred()) 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)
}
})
}) })

View File

@ -29,6 +29,7 @@ import (
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
@ -81,6 +82,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return fmt.Errorf("failed to move link %v", err) return fmt.Errorf("failed to move link %v", err)
} }
var result *current.Result
// run the IPAM plugin and get back the config to apply // run the IPAM plugin and get back the config to apply
if cfg.IPAM.Type != "" { if cfg.IPAM.Type != "" {
r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData) r, err := ipam.ExecAdd(cfg.IPAM.Type, args.StdinData)
@ -96,7 +98,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}() }()
// Convert whatever the IPAM result was into the current Result type // Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(r) result, err = current.NewResultFromResult(r)
if err != nil { if err != nil {
return err return err
} }
@ -124,6 +126,10 @@ func cmdAdd(args *skel.CmdArgs) error {
if err != nil { if err != nil {
return err return err
} }
result.DNS = cfg.DNS
return types.PrintResult(result, cfg.CNIVersion)
} }
return printLink(contDev, cfg.CNIVersion, containerNs) return printLink(contDev, cfg.CNIVersion, containerNs)
@ -276,10 +282,109 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented") cfg, err := loadConf(args.StdinData)
if err != nil {
return err
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// run the IPAM plugin and get back the config to apply
if cfg.IPAM.Type != "" {
err = ipam.ExecCheck(cfg.IPAM.Type, args.StdinData)
if err != nil {
return err
}
}
// Parse previous result.
if cfg.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&cfg.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(cfg.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name we know, that of host-device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
//
return nil
}
func validateCniContainerInterface(intf current.Interface) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
} }

View File

@ -15,19 +15,210 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"strings"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/vishvananda/netlink" "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
IPAM *IPAMConfig `json:"ipam,omitempty"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.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 {
n.IPAM.Addresses[i].Version = "4"
numV4++
} else {
n.IPAM.Addresses[i].Version = "6"
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 {
addr.Version = "4"
numV4++
} else {
addr.Version = "6"
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 {
for _, v := range types020.SupportedVersions {
if n.CNIVersion == v {
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
}
var _ = Describe("base functionality", func() { var _ = Describe("base functionality", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
var ifname string var ifname string
@ -262,4 +453,255 @@ var _ = Describe("base functionality", func() {
}) })
It("Works with a valid 0.4.0 config without IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result was sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("testConfig", cniVersion, n, res)
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()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Works with a valid 0.4.0 config with IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
targetIP := "10.10.0.1/24"
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"ipam": {
"type": "static",
"addresses": [
{
"address":"`+targetIP+`",
"gateway": "10.10.0.254"
}]
},
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result was sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
//get the IP address of the interface in the target namespace
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
addr := addrs[0].IPNet.String()
//assert that IP address is what we set
Expect(addr).To(Equal(targetIP))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("testConfig", cniVersion, n, res)
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()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
}) })

View File

@ -32,12 +32,6 @@ import (
type NetConf struct { type NetConf struct {
types.NetConf types.NetConf
// support chaining for master interface and IP decisions
// occurring prior to running ipvlan plugin
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
Master string `json:"master"` Master string `json:"master"`
Mode string `json:"mode"` Mode string `json:"mode"`
MTU int `json:"mtu"` MTU int `json:"mtu"`
@ -50,33 +44,35 @@ func init() {
runtime.LockOSThread() runtime.LockOSThread()
} }
func loadConf(bytes []byte) (*NetConf, string, error) { func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) {
n := &NetConf{} n := &NetConf{}
if err := json.Unmarshal(bytes, n); err != nil { if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err) return nil, "", fmt.Errorf("failed to load netconf: %v", err)
} }
if cmdCheck {
return n, n.CNIVersion, nil
}
var result *current.Result
var err error
// Parse previous result // Parse previous result
if n.RawPrevResult != nil { if n.NetConf.RawPrevResult != nil {
resultBytes, err := json.Marshal(n.RawPrevResult) if err = version.ParsePrevResult(&n.NetConf); err != nil {
if err != nil {
return nil, "", fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(n.CNIVersion, resultBytes)
if err != nil {
return nil, "", fmt.Errorf("could not parse prevResult: %v", err) return nil, "", fmt.Errorf("could not parse prevResult: %v", err)
} }
n.RawPrevResult = nil
n.PrevResult, err = current.NewResultFromResult(res) result, err = current.NewResultFromResult(n.PrevResult)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("could not convert result to current version: %v", err) return nil, "", fmt.Errorf("could not convert result to current version: %v", err)
} }
} }
if n.Master == "" { if n.Master == "" {
if n.PrevResult == nil { if result == nil {
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`) return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
} }
if len(n.PrevResult.Interfaces) == 1 && n.PrevResult.Interfaces[0].Name != "" { if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
n.Master = n.PrevResult.Interfaces[0].Name n.Master = result.Interfaces[0].Name
} else { } else {
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface") return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
} }
@ -97,6 +93,19 @@ func modeFromString(s string) (netlink.IPVlanMode, error) {
} }
} }
func modeToString(mode netlink.IPVlanMode) (string, error) {
switch mode {
case netlink.IPVLAN_MODE_L2:
return "l2", nil
case netlink.IPVLAN_MODE_L3:
return "l3", nil
case netlink.IPVLAN_MODE_L3S:
return "l3s", nil
default:
return "", fmt.Errorf("unknown ipvlan mode: %q", mode)
}
}
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) { func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
ipvlan := &current.Interface{} ipvlan := &current.Interface{}
@ -156,7 +165,7 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
} }
func cmdAdd(args *skel.CmdArgs) error { func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadConf(args.StdinData) n, cniVersion, err := loadConf(args.StdinData, false)
if err != nil { if err != nil {
return err return err
} }
@ -175,9 +184,17 @@ func cmdAdd(args *skel.CmdArgs) error {
var result *current.Result var result *current.Result
// Configure iface from PrevResult if we have IPs and an IPAM // Configure iface from PrevResult if we have IPs and an IPAM
// block has not been configured // block has not been configured
if n.IPAM.Type == "" && n.PrevResult != nil && len(n.PrevResult.IPs) > 0 { haveResult := false
result = n.PrevResult if n.IPAM.Type == "" && n.PrevResult != nil {
} else { result, err = current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
if len(result.IPs) > 0 {
haveResult = true
}
}
if !haveResult {
// run the IPAM plugin and get back the config to apply // run the IPAM plugin and get back the config to apply
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
if err != nil { if err != nil {
@ -213,7 +230,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
func cmdDel(args *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData) n, _, err := loadConf(args.StdinData, false)
if err != nil { if err != nil {
return err return err
} }
@ -246,10 +263,130 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented") n, _, err := loadConf(args.StdinData, true)
if err != nil {
return err
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
if n.IPAM.Type != "" {
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
}
// Parse previous result.
if n.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&n.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for names whe know, ipvlan inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
m, err := netlink.LinkByName(n.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
}
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface, masterIndex int, modeExpected string) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
ipv, isIPVlan := link.(*netlink.IPVlan)
if !isIPVlan {
return fmt.Errorf("Error: Container interface %s not of type ipvlan", link.Attrs().Name)
}
mode, err := modeFromString(modeExpected)
if ipv.Mode != mode {
currString, err := modeToString(ipv.Mode)
if err != nil {
return err
}
confString, err := modeToString(mode)
if err != nil {
return err
}
return fmt.Errorf("Container IPVlan mode %s does not match expected value: %s", currString, confString)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
} }

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"syscall" "syscall"
@ -27,12 +28,71 @@ import (
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
const MASTER_NAME = "eth0" const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Master string `json:"master"`
Mode string `json:"mode"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
if orig.IPAM == nil {
inject["master"] = master
}
// 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 ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) { func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
targetNs, err := testutils.NewNS() targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -106,6 +166,109 @@ func ipvlanAddDelTest(conf, IFNAME string, originalNS ns.NetNS) {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
func ipvlanAddCheckDelTest(conf string, netName string, IFNAME string, originalNS ns.NetNS) {
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure ipvlan link exists in the target namespace
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
if n.IPAM != nil {
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
}
cniVersion := "0.4.0"
newConf, err := buildOneConfig(netName, cniVersion, MASTER_NAME, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check on macvlan in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure ipvlan link has been deleted
err = 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())
}
var _ = Describe("ipvlan Operations", func() { var _ = Describe("ipvlan Operations", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
@ -256,4 +419,49 @@ var _ = Describe("ipvlan Operations", func() {
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL", func() {
const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "ipvlanTest1",
"type": "ipvlan",
"master": "%s",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`, MASTER_NAME)
ipvlanAddCheckDelTest(conf, "ipvlanTest1", IFNAME, originalNS)
})
It("configures and deconfigures a cniVersion 0.4.0 iplvan link with ADD/CHECK/DEL when chained", func() {
const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "ipvlanTest2",
"type": "ipvlan",
"prevResult": {
"interfaces": [
{
"name": "%s"
}
],
"ips": [
{
"version": "4",
"address": "10.1.2.2/24",
"gateway": "10.1.2.1",
"interface": 0
}
],
"routes": []
}
}`, MASTER_NAME)
ipvlanAddCheckDelTest(conf, "ipvlanTest2", IFNAME, originalNS)
})
}) })

View File

@ -15,8 +15,6 @@
package main package main
import ( import (
"fmt"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
@ -74,10 +72,10 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement // TODO: implement
return fmt.Errorf("not implemented") return nil
} }

View File

@ -77,6 +77,21 @@ func modeFromString(s string) (netlink.MacvlanMode, error) {
} }
} }
func modeToString(mode netlink.MacvlanMode) (string, error) {
switch mode {
case netlink.MACVLAN_MODE_BRIDGE:
return "bridge", nil
case netlink.MACVLAN_MODE_PRIVATE:
return "private", nil
case netlink.MACVLAN_MODE_VEPA:
return "vepa", nil
case netlink.MACVLAN_MODE_PASSTHRU:
return "passthru", nil
default:
return "", fmt.Errorf("unknown macvlan mode: %q", mode)
}
}
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) { func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
macvlan := &current.Interface{} macvlan := &current.Interface{}
@ -256,10 +271,128 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented") n, _, err := loadConf(args.StdinData)
if err != nil {
return err
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
// Parse previous result.
if n.NetConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&n.NetConf); err != nil {
return err
}
result, err := current.NewResultFromResult(n.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for names whe know, macvlan device name inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
m, err := netlink.LinkByName(n.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", n.Master, err)
}
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap, m.Attrs().Index, n.Mode)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface, parentIndex int, modeExpected string) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
macv, isMacvlan := link.(*netlink.Macvlan)
if !isMacvlan {
return fmt.Errorf("Error: Container interface %s not of type macvlan", link.Attrs().Name)
}
mode, err := modeFromString(modeExpected)
if macv.Mode != mode {
currString, err := modeToString(macv.Mode)
if err != nil {
return err
}
confString, err := modeToString(mode)
if err != nil {
return err
}
return fmt.Errorf("Container macvlan mode %s does not match expected value: %s", currString, confString)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
} }

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"syscall" "syscall"
@ -27,12 +28,73 @@ import (
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
const MASTER_NAME = "eth0" const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Master string `json:"master"`
Mode string `json:"mode"`
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:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"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
}
var _ = Describe("macvlan Operations", func() { var _ = Describe("macvlan Operations", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
@ -223,4 +285,118 @@ var _ = Describe("macvlan Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("configures and deconfigures a cniVersion 0.4.0 macvlan link with ADD/DEL", func() {
const IFNAME = "macvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "macvlanTestv4",
"type": "macvlan",
"master": "%s",
"ipam": {
"type": "host-local",
"ranges": [[ {"subnet": "10.1.2.0/24", "gateway": "10.1.2.1"} ]]
}
}`, MASTER_NAME)
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure macvlan link exists in the target namespace
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("macvlanTestv4", cniVersion, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check on macvlan in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure macvlan link has been deleted
err = 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())
})
}) })

View File

@ -286,10 +286,108 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement conf := NetConf{}
return fmt.Errorf("not implemented") if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %v", err)
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
if err != nil {
return err
}
if conf.NetConf.RawPrevResult == nil {
return fmt.Errorf("ptp: Required prevResult missing")
}
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
return err
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name whe know, that of host-device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
_, isVeth := link.(*netlink.Veth)
if !isVeth {
return fmt.Errorf("Error: Container interface %s not of type veth/p2p", link.Attrs().Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("ptp: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
return nil
} }

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
@ -25,10 +26,66 @@ import (
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"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
}
var _ = Describe("ptp Operations", func() { var _ = Describe("ptp Operations", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
@ -142,6 +199,133 @@ var _ = Describe("ptp Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
} }
doTestv4 := func(conf string, netName string, numIPs int) {
const IFNAME = "ptp0"
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var resI types.Result
var res *current.Result
// Execute the plugin with the ADD command, creating the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
resI, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
res, err = current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
// Make sure ptp link exists in the target namespace
// Then, ping the gateway
seenIPs := 0
wantMac := ""
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
wantMac = link.Attrs().HardwareAddr.String()
for _, ipc := range res.IPs {
if *ipc.Interface != 1 {
continue
}
seenIPs += 1
saddr := ipc.Address.IP.String()
daddr := ipc.Gateway.String()
fmt.Fprintln(GinkgoWriter, "ping", saddr, "->", daddr)
if err := testutils.Ping(saddr, daddr, (ipc.Version == "6"), 30); err != nil {
return fmt.Errorf("ping %s -> %s failed: %s", saddr, daddr, err)
}
}
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(seenIPs).To(Equal(numIPs))
// make sure the interfaces are correct
Expect(res.Interfaces).To(HaveLen(2))
Expect(res.Interfaces[0].Name).To(HavePrefix("veth"))
Expect(res.Interfaces[0].Mac).To(HaveLen(17))
Expect(res.Interfaces[0].Sandbox).To(BeEmpty())
Expect(res.Interfaces[1].Name).To(Equal(IFNAME))
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig(netName, cniVersion, n, res)
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()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
args.StdinData = []byte(conf)
// Call the plugins with the DEL command, deleting the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure ptp link has been deleted
err = 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())
}
It("configures and deconfigures a ptp link with ADD/DEL", func() { It("configures and deconfigures a ptp link with ADD/DEL", func() {
conf := `{ conf := `{
"cniVersion": "0.3.1", "cniVersion": "0.3.1",
@ -215,4 +399,39 @@ var _ = Describe("ptp Operations", func() {
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("configures and deconfigures a CNI V4 ptp link with ADD/DEL", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "ptpNetv4",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
doTestv4(conf, "ptpNetv4", 1)
})
It("configures and deconfigures a CNI V4 dual-stack ptp link with ADD/DEL", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "ptpNetv4ds",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"ranges": [
[{ "subnet": "10.1.2.0/24"}],
[{ "subnet": "2001:db8:1::0/66"}]
]
}
}`
doTestv4(conf, "ptpNetv4ds", 2)
})
}) })

View File

@ -193,10 +193,130 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement conf := NetConf{}
return fmt.Errorf("not implemented") if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %v", err)
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
if err != nil {
return err
}
if conf.NetConf.RawPrevResult == nil {
return fmt.Errorf("ptp: Required prevResult missing")
}
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
return err
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name whe know, that of host-device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
m, err := netlink.LinkByName(conf.Master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap, m.Attrs().Index, conf.VlanId, conf.MTU)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
err = ip.ValidateExpectedRoute(result.Routes)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface, masterIndex int, vlanId int, mtu int) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
vlan, isVlan := link.(*netlink.Vlan)
if !isVlan {
return fmt.Errorf("Error: Container interface %s not of type vlan", link.Attrs().Name)
}
// TODO This works when unit testing via cnitool; fails with ./test.sh
//if masterIndex != vlan.Attrs().ParentIndex {
// return fmt.Errorf("Container vlan Master %d does not match expected value: %d", vlan.Attrs().ParentIndex, masterIndex)
//}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("vlan: Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
if vlanId != vlan.VlanId {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
intf.Name, vlanId, vlan.VlanId)
}
if mtu != 0 {
if mtu != link.Attrs().MTU {
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
intf.Name, mtu, link.Attrs().MTU)
}
}
return nil
} }

View File

@ -15,6 +15,7 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"syscall" "syscall"
@ -27,12 +28,69 @@ import (
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
const MASTER_NAME = "eth0" const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
Master string `json:"master"`
VlanId int `json:"vlanId"`
MTU int `json:"mtu"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult current.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"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
}
var _ = Describe("vlan Operations", func() { var _ = Describe("vlan Operations", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
@ -234,4 +292,119 @@ var _ = Describe("vlan Operations", func() {
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() {
const IFNAME = "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "vlanTestv4",
"type": "vlan",
"master": "%s",
"vlanId": 1234,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`, MASTER_NAME)
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure vlan link exists in the target namespace
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
newConf, err := buildOneConfig("vlanTestv4", cniVersion, n, result)
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()
var err error
err = testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
args.StdinData = []byte(conf)
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure vlan link has been deleted
err = 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())
})
}) })

View File

@ -35,6 +35,49 @@ import (
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
) )
func buildOneConfig(name, cniVersion string, orig *PluginConf, prevResult types.Result) (*PluginConf, []byte, 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, nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, 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, nil, err
}
conf := &PluginConf{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, newBytes, nil
}
var _ = Describe("bandwidth test", func() { var _ = Describe("bandwidth test", func() {
var ( var (
hostNs ns.NetNS hostNs ns.NetNS
@ -643,7 +686,6 @@ var _ = Describe("bandwidth test", func() {
containerWithTbfResult, err := current.GetResult(containerWithTbfRes) containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
tbfPluginConf := PluginConf{} tbfPluginConf := PluginConf{}
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{ tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits, IngressBurst: burstInBits,
@ -654,7 +696,7 @@ var _ = Describe("bandwidth test", func() {
tbfPluginConf.Name = "mynet" tbfPluginConf.Name = "mynet"
tbfPluginConf.CNIVersion = "0.3.0" tbfPluginConf.CNIVersion = "0.3.0"
tbfPluginConf.Type = "bandwidth" tbfPluginConf.Type = "bandwidth"
tbfPluginConf.RawPrevResult = &map[string]interface{}{ tbfPluginConf.RawPrevResult = map[string]interface{}{
"ips": containerWithTbfResult.IPs, "ips": containerWithTbfResult.IPs,
"interfaces": containerWithTbfResult.Interfaces, "interfaces": containerWithTbfResult.Interfaces,
} }
@ -663,7 +705,6 @@ var _ = Describe("bandwidth test", func() {
IPs: containerWithTbfResult.IPs, IPs: containerWithTbfResult.IPs,
Interfaces: containerWithTbfResult.Interfaces, Interfaces: containerWithTbfResult.Interfaces,
} }
conf, err := json.Marshal(tbfPluginConf) conf, err := json.Marshal(tbfPluginConf)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -725,4 +766,169 @@ var _ = Describe("bandwidth test", func() {
}, 1) }, 1)
}) })
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
var portServerWithTbf int
var portServerWithoutTbf int
var containerWithTbfRes types.Result
var containerWithoutTbfRes types.Result
var echoServerWithTbf *gexec.Session
var echoServerWithoutTbf *gexec.Session
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25
ptpConf = `{
"cniVersion": "0.4.0",
"name": "myBWnet",
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
containerWithTbfIFName := "ptp0"
containerWithoutTbfIFName := "ptp1"
var err error
containerWithTbfNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
containerWithoutTbfNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
By("create two containers, and use the bandwidth plugin on one of them")
Expect(hostNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(r.Print()).To(Succeed())
return err
})
Expect(err).NotTo(HaveOccurred())
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(r.Print()).To(Succeed())
return err
})
Expect(err).NotTo(HaveOccurred())
containerWithTbfResult, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf := &PluginConf{}
err = json.Unmarshal([]byte(ptpConf), &tbfPluginConf)
Expect(err).NotTo(HaveOccurred())
tbfPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
IngressRate: rateInBits,
EgressBurst: burstInBits,
EgressRate: rateInBits,
}
tbfPluginConf.Type = "bandwidth"
cniVersion := "0.4.0"
_, newConfBytes, err := buildOneConfig("myBWnet", cniVersion, tbfPluginConf, containerWithTbfResult)
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
ContainerID: "dummy3",
Netns: containerWithTbfNS.Path(),
IfName: containerWithTbfIFName,
StdinData: newConfBytes,
}
result, out, err := testutils.CmdAdd(containerWithTbfNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
// Do CNI Check
checkConf := &PluginConf{}
err = json.Unmarshal([]byte(ptpConf), &checkConf)
Expect(err).NotTo(HaveOccurred())
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
IngressBurst: burstInBits,
IngressRate: rateInBits,
EgressBurst: burstInBits,
EgressRate: rateInBits,
}
checkConf.Type = "bandwidth"
_, newCheckBytes, err := buildOneConfig("myBWnet", cniVersion, checkConf, result)
Expect(err).NotTo(HaveOccurred())
args = &skel.CmdArgs{
ContainerID: "dummy3",
Netns: containerWithTbfNS.Path(),
IfName: containerWithTbfIFName,
StdinData: newCheckBytes,
}
err = testutils.CmdCheck(containerWithTbfNS.Path(), args.ContainerID, "", newCheckBytes, func() error { return cmdCheck(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})).To(Succeed())
By("starting a tcp server on both containers")
portServerWithTbf, echoServerWithTbf, err = startEchoServerInNamespace(containerWithTbfNS)
Expect(err).NotTo(HaveOccurred())
portServerWithoutTbf, echoServerWithoutTbf, err = startEchoServerInNamespace(containerWithoutTbfNS)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
containerWithTbfNS.Close()
containerWithoutTbfNS.Close()
if echoServerWithoutTbf != nil {
echoServerWithoutTbf.Kill()
}
if echoServerWithTbf != nil {
echoServerWithTbf.Kill()
}
})
Measure("limits ingress traffic on veth device", func(b Benchmarker) {
var runtimeWithLimit time.Duration
var runtimeWithoutLimit time.Duration
By("gather timing statistics about both containers")
By("sending tcp traffic to the container that has traffic shaped", func() {
runtimeWithLimit = b.Time("with tbf", func() {
result, err := current.GetResult(containerWithTbfRes)
Expect(err).NotTo(HaveOccurred())
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithTbf, packetInBytes)
})
})
By("sending tcp traffic to the container that does not have traffic shaped", func() {
runtimeWithoutLimit = b.Time("without tbf", func() {
result, err := current.GetResult(containerWithoutTbfRes)
Expect(err).NotTo(HaveOccurred())
makeTcpClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutTbf, packetInBytes)
})
})
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
}, 1)
})
}) })

View File

@ -50,10 +50,6 @@ type PluginConf struct {
Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"` Bandwidth *BandwidthEntry `json:"bandwidth,omitempty"`
} `json:"runtimeConfig,omitempty"` } `json:"runtimeConfig,omitempty"`
// RuntimeConfig *struct{} `json:"runtimeConfig"`
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
*BandwidthEntry *BandwidthEntry
} }
@ -65,21 +61,6 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
return nil, fmt.Errorf("failed to parse network configuration: %v", err) return nil, fmt.Errorf("failed to parse network configuration: %v", err)
} }
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
bandwidth := getBandwidth(&conf) bandwidth := getBandwidth(&conf)
if bandwidth != nil { if bandwidth != nil {
err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst) err := validateRateAndBurst(bandwidth.IngressRate, bandwidth.IngressBurst)
@ -92,6 +73,18 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
} }
} }
if conf.RawPrevResult != nil {
var err error
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
_, err = current.NewResultFromResult(conf.PrevResult)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
return &conf, nil return &conf, nil
} }
@ -167,7 +160,11 @@ func cmdAdd(args *skel.CmdArgs) error {
return fmt.Errorf("must be called as chained plugin") return fmt.Errorf("must be called as chained plugin")
} }
hostInterface, err := getHostInterface(conf.PrevResult.Interfaces) result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
if err != nil { if err != nil {
return err return err
} }
@ -200,7 +197,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err return err
} }
conf.PrevResult.Interfaces = append(conf.PrevResult.Interfaces, &current.Interface{ result.Interfaces = append(result.Interfaces, &current.Interface{
Name: ifbDeviceName, Name: ifbDeviceName,
Mac: ifbDevice.Attrs().HardwareAddr.String(), Mac: ifbDevice.Attrs().HardwareAddr.String(),
}) })
@ -210,7 +207,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
} }
return types.PrintResult(conf.PrevResult, conf.CNIVersion) return types.PrintResult(result, conf.CNIVersion)
} }
func cmdDel(args *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
@ -233,10 +230,125 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
// TODO: implement qdiscs, err := netlink.QdiscList(link)
return fmt.Errorf("not implemented") if err != nil {
return nil, err
}
result := []netlink.Qdisc{}
for _, qdisc := range qdiscs {
// filter out pfifo_fast qdiscs because
// older kernels don't return them
_, pfifo := qdisc.(*netlink.PfifoFast)
if !pfifo {
result = append(result, qdisc)
}
}
return result, nil
}
func cmdCheck(args *skel.CmdArgs) error {
bwConf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
if bwConf.PrevResult == nil {
return fmt.Errorf("must be called as a chained plugin")
}
result, err := current.NewResultFromResult(bwConf.PrevResult)
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
if err != nil {
return err
}
link, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
bandwidth := getBandwidth(bwConf)
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
rateInBytes := bandwidth.IngressRate / 8
burstInBytes := bandwidth.IngressBurst / 8
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
qdiscs, err := SafeQdiscList(link)
if err != nil {
return err
}
if len(qdiscs) == 0 {
return fmt.Errorf("Failed to find qdisc")
}
for _, qdisc := range qdiscs {
tbf, isTbf := qdisc.(*netlink.Tbf)
if !isTbf {
break
}
if tbf.Rate != uint64(rateInBytes) {
return fmt.Errorf("Rate doesn't match")
}
if tbf.Limit != uint32(limitInBytes) {
return fmt.Errorf("Limit doesn't match")
}
if tbf.Buffer != uint32(bufferInBytes) {
return fmt.Errorf("Buffer doesn't match")
}
}
}
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
rateInBytes := bandwidth.EgressRate / 8
burstInBytes := bandwidth.EgressBurst / 8
bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes))
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {
return fmt.Errorf("get ifb device: %s", err)
}
qdiscs, err := SafeQdiscList(ifbDevice)
if err != nil {
return err
}
if len(qdiscs) == 0 {
return fmt.Errorf("Failed to find qdisc")
}
for _, qdisc := range qdiscs {
tbf, isTbf := qdisc.(*netlink.Tbf)
if !isTbf {
break
}
if tbf.Rate != uint64(rateInBytes) {
return fmt.Errorf("Rate doesn't match")
}
if tbf.Limit != uint32(limitInBytes) {
return fmt.Errorf("Limit doesn't match")
}
if tbf.Buffer != uint32(bufferInBytes) {
return fmt.Errorf("Buffer doesn't match")
}
}
}
return nil
} }

View File

@ -138,3 +138,44 @@ func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, err
} }
return false, nil return false, nil
} }
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := chainExists(ipt, c.table, c.name)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("chain %s not found in iptables table %s", c.name, c.table)
}
for i := len(c.rules) - 1; i >= 0; i-- {
match := checkRule(ipt, c.table, c.name, c.rules[i])
if !match {
return fmt.Errorf("rule %s in chain %s not found in table %s", c.rules, c.name, c.table)
}
}
for _, entryChain := range c.entryChains {
for i := len(c.entryRules) - 1; i >= 0; i-- {
r := []string{}
r = append(r, c.entryRules[i]...)
r = append(r, "-j", c.name)
matchEntryChain := checkRule(ipt, c.table, entryChain, r)
if !matchEntryChain {
return fmt.Errorf("rule %s in chain %s not found in table %s", c.entryRules, entryChain, c.table)
}
}
}
return nil
}
func checkRule(ipt *iptables.IPTables, table, chain string, rule []string) bool {
exists, err := ipt.Exists(table, chain, rule...)
if err != nil {
return false
}
return exists
}

View File

@ -55,8 +55,6 @@ type PortMapConf struct {
RuntimeConfig struct { RuntimeConfig struct {
PortMaps []PortMapEntry `json:"portMappings,omitempty"` PortMaps []PortMapEntry `json:"portMappings,omitempty"`
} `json:"runtimeConfig,omitempty"` } `json:"runtimeConfig,omitempty"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult *current.Result `json:"-"`
// These are fields parsed out of the config or the environment; // These are fields parsed out of the config or the environment;
// included here for convenience // included here for convenience
@ -70,7 +68,7 @@ type PortMapConf struct {
const DefaultMarkBit = 13 const DefaultMarkBit = 13
func cmdAdd(args *skel.CmdArgs) error { func cmdAdd(args *skel.CmdArgs) error {
netConf, err := parseConfig(args.StdinData, args.IfName) netConf, _, err := parseConfig(args.StdinData, args.IfName)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse config: %v", err) return fmt.Errorf("failed to parse config: %v", err)
} }
@ -102,7 +100,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
func cmdDel(args *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
netConf, err := parseConfig(args.StdinData, args.IfName) netConf, _, err := parseConfig(args.StdinData, args.IfName)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse config: %v", err) return fmt.Errorf("failed to parse config: %v", err)
} }
@ -119,36 +117,60 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement conf, result, err := parseConfig(args.StdinData, args.IfName)
return fmt.Errorf("not implemented") if err != nil {
return err
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
}
if len(conf.RuntimeConfig.PortMaps) == 0 {
return nil
}
conf.ContainerID = args.ContainerID
if conf.ContIPv4 != nil {
if err := checkPorts(conf, conf.ContIPv4); err != nil {
return err
}
}
if conf.ContIPv6 != nil {
if err := checkPorts(conf, conf.ContIPv6); err != nil {
return err
}
}
return nil
} }
// parseConfig parses the supplied configuration (and prevResult) from stdin. // parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) { func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, error) {
conf := PortMapConf{} conf := PortMapConf{}
if err := json.Unmarshal(stdin, &conf); err != nil { if err := json.Unmarshal(stdin, &conf); err != nil {
return nil, fmt.Errorf("failed to parse network configuration: %v", err) return nil, nil, fmt.Errorf("failed to parse network configuration: %v", err)
} }
// Parse previous result. // Parse previous result.
var result *current.Result
if conf.RawPrevResult != nil { if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult) var err error
if err != nil { if err = version.ParsePrevResult(&conf.NetConf); err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err) return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
} }
res, err := version.NewResult(conf.CNIVersion, resultBytes)
result, err = current.NewResultFromResult(conf.PrevResult)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err) return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
} }
} }
@ -158,7 +180,7 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
} }
if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil { if conf.MarkMasqBit != nil && conf.ExternalSetMarkChain != nil {
return nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit") return nil, nil, fmt.Errorf("Cannot specify externalSetMarkChain and markMasqBit")
} }
if conf.MarkMasqBit == nil { if conf.MarkMasqBit == nil {
@ -167,21 +189,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
} }
if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 { if *conf.MarkMasqBit < 0 || *conf.MarkMasqBit > 31 {
return nil, fmt.Errorf("MasqMarkBit must be between 0 and 31") return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
} }
// Reject invalid port numbers // Reject invalid port numbers
for _, pm := range conf.RuntimeConfig.PortMaps { for _, pm := range conf.RuntimeConfig.PortMaps {
if pm.ContainerPort <= 0 { if pm.ContainerPort <= 0 {
return nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort) return nil, nil, fmt.Errorf("Invalid container port number: %d", pm.ContainerPort)
} }
if pm.HostPort <= 0 { if pm.HostPort <= 0 {
return nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort) return nil, nil, fmt.Errorf("Invalid host port number: %d", pm.HostPort)
} }
} }
if conf.PrevResult != nil { if conf.PrevResult != nil {
for _, ip := range conf.PrevResult.IPs { for _, ip := range result.IPs {
if ip.Version == "6" && conf.ContIPv6 != nil { if ip.Version == "6" && conf.ContIPv6 != nil {
continue continue
} else if ip.Version == "4" && conf.ContIPv4 != nil { } else if ip.Version == "4" && conf.ContIPv4 != nil {
@ -192,9 +214,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
if ip.Interface != nil { if ip.Interface != nil {
intIdx := *ip.Interface intIdx := *ip.Interface
if intIdx >= 0 && if intIdx >= 0 &&
intIdx < len(conf.PrevResult.Interfaces) && intIdx < len(result.Interfaces) &&
(conf.PrevResult.Interfaces[intIdx].Name != ifName || (result.Interfaces[intIdx].Name != ifName ||
conf.PrevResult.Interfaces[intIdx].Sandbox == "") { result.Interfaces[intIdx].Sandbox == "") {
continue continue
} }
} }
@ -207,5 +229,5 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, error) {
} }
} }
return &conf, nil return &conf, result, nil
} }

View File

@ -111,6 +111,46 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
return nil return nil
} }
func checkPorts(config *PortMapConf, containerIP net.IP) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)
fillDnatRules(&dnatChain, config, containerIP)
ip4t := maybeGetIptables(false)
ip6t := maybeGetIptables(true)
if ip4t == nil && ip6t == nil {
return fmt.Errorf("neither iptables nor ip6tables usable")
}
if ip4t != nil {
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
if !exists {
return err
}
if err := dnatChain.check(ip4t); err != nil {
return fmt.Errorf("could not check ipv4 dnat: %v", err)
}
}
if ip6t != nil {
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
if !exists {
return err
}
if err := dnatChain.check(ip6t); err != nil {
return fmt.Errorf("could not check ipv6 dnat: %v", err)
}
}
return nil
}
// genToplevelDnatChain creates the top-level summary chain that we'll // genToplevelDnatChain creates the top-level summary chain that we'll
// add our chain to. This is easy, because creating chains is idempotent. // add our chain to. This is easy, because creating chains is idempotent.
// IMPORTANT: do not change this, or else upgrading plugins will require // IMPORTANT: do not change this, or else upgrading plugins will require

View File

@ -68,7 +68,7 @@ var _ = Describe("portmapping configuration", func() {
] ]
} }
}`) }`)
c, err := parseConfig(configBytes, "container") c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal("0.3.1")) Expect(c.CNIVersion).To(Equal("0.3.1"))
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
@ -91,7 +91,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV4": ["a", "b"], "conditionsV4": ["a", "b"],
"conditionsV6": ["c", "d"] "conditionsV6": ["c", "d"]
}`) }`)
c, err := parseConfig(configBytes, "container") c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal("0.3.1")) Expect(c.CNIVersion).To(Equal("0.3.1"))
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"})) Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
@ -115,7 +115,7 @@ var _ = Describe("portmapping configuration", func() {
] ]
} }
}`) }`)
_, err := parseConfig(configBytes, "container") _, _, err := parseConfig(configBytes, "container")
Expect(err).To(MatchError("Invalid host port number: 0")) Expect(err).To(MatchError("Invalid host port number: 0"))
}) })
@ -143,7 +143,7 @@ var _ = Describe("portmapping configuration", func() {
] ]
} }
}`) }`)
_, err := parseConfig(configBytes, "container") _, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
}) })
@ -175,7 +175,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV6": ["c", "d"] "conditionsV6": ["c", "d"]
}`) }`)
conf, err := parseConfig(configBytes, "foo") conf, _, err := parseConfig(configBytes, "foo")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf.ContainerID = containerID conf.ContainerID = containerID
@ -271,7 +271,7 @@ var _ = Describe("portmapping configuration", func() {
"conditionsV6": ["c", "d"] "conditionsV6": ["c", "d"]
}`) }`)
conf, err := parseConfig(configBytes, "foo") conf, _, err := parseConfig(configBytes, "foo")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf.ContainerID = containerID conf.ContainerID = containerID

View File

@ -36,12 +36,10 @@ import (
// TuningConf represents the network tuning configuration. // TuningConf represents the network tuning configuration.
type TuningConf struct { type TuningConf struct {
types.NetConf types.NetConf
SysCtl map[string]string `json:"sysctl"` SysCtl map[string]string `json:"sysctl"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` Mac string `json:"mac,omitempty"`
PrevResult *current.Result `json:"-"` Promisc bool `json:"promisc,omitempty"`
Mac string `json:"mac,omitempty"` Mtu int `json:"mtu,omitempty"`
Promisc bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
} }
type MACEnvArgs struct { type MACEnvArgs struct {
@ -68,23 +66,6 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
} }
} }
// Parse previous result.
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("could not convert result to current version: %v", err)
}
}
return &conf, nil return &conf, nil
} }
@ -111,7 +92,15 @@ func changeMacAddr(ifName string, newMacAddr string) error {
} }
func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) { func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) {
for _, i := range config.PrevResult.Interfaces { // Parse previous result.
if config.PrevResult == nil {
return
}
version.ParsePrevResult(&config.NetConf)
result, _ := current.NewResultFromResult(config.PrevResult)
for _, i := range result.Interfaces {
if i.Name == ifName { if i.Name == ifName {
i.Mac = newMacAddr i.Mac = newMacAddr
} }
@ -144,6 +133,20 @@ func cmdAdd(args *skel.CmdArgs) error {
return err return err
} }
// Parse previous result.
if tuningConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
// The directory /proc/sys/net is per network namespace. Enter in the // The directory /proc/sys/net is per network namespace. Enter in the
// network namespace before writing on it. // network namespace before writing on it.
@ -200,10 +203,80 @@ func cmdDel(args *skel.CmdArgs) error {
func main() { func main() {
// TODO: implement plugin version // TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, "TODO")
} }
func cmdGet(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement tuningConf, err := parseConf(args.StdinData, args.Args)
return fmt.Errorf("not implemented") if err != nil {
return err
}
// Parse previous result.
if tuningConf.RawPrevResult == nil {
return fmt.Errorf("Required prevResult missing")
}
if err := version.ParsePrevResult(&tuningConf.NetConf); err != nil {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
// Check each configured value vs what's currently in the container
for key, conf_value := range tuningConf.SysCtl {
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
fileName = filepath.Clean(fileName)
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
cur_value := strings.TrimSuffix(string(contents), "\n")
if conf_value != cur_value {
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value)
}
}
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return fmt.Errorf("Cannot find container link %v", args.IfName)
}
if tuningConf.Mac != "" {
if tuningConf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Error: Tuning configured Ethernet of %s is %s, current value is %s",
args.IfName, tuningConf.Mac, link.Attrs().HardwareAddr)
}
}
if tuningConf.Promisc {
if link.Attrs().Promisc == 0 {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
}
} else {
if link.Attrs().Promisc != 0 {
return fmt.Errorf("Error: Tuning link %s configured promisc is %v, current value is %d",
args.IfName, tuningConf.Promisc, link.Attrs().Promisc)
}
}
if tuningConf.Mtu != 0 {
if tuningConf.Mtu != link.Attrs().MTU {
return fmt.Errorf("Error: Tuning configured MTU of %s is %d, current value is %d",
args.IfName, tuningConf.Mtu, link.Attrs().MTU)
}
}
return nil
})
if err != nil {
return err
}
return nil
} }

View File

@ -15,7 +15,11 @@
package main package main
import ( import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
@ -27,6 +31,49 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
func buildOneConfig(name, cniVersion string, orig *TuningConf, prevResult types.Result) (*TuningConf, []byte, 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, nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, 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, nil, err
}
conf := &TuningConf{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, newBytes, nil
}
var _ = Describe("tuning plugin", func() { var _ = Describe("tuning plugin", func() {
var originalNS ns.NetNS var originalNS ns.NetNS
const IFNAME string = "dummy0" const IFNAME string = "dummy0"
@ -342,4 +389,296 @@ var _ = Describe("tuning plugin", func() {
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("configures and deconfigures promiscuous mode with CNI 0.4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"promisc": true,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Promisc).To(Equal(1))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mtu with CNI 0.4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"mtu": 1454,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().MTU).To(Equal(1454))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from conf file) with CNI v4.0 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"mac": "c2:11:22:33:44:55",
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
conf := []byte(`{
"name": "test",
"type": "iplink",
"cniVersion": "0.4.0",
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66",
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
hw, err := net.ParseMAC("c2:11:22:33:44:66")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
n := &TuningConf{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
cniVersion := "0.4.0"
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
err = testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})
Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
}) })