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:
parent
82a0651d0a
commit
74a2596573
120
pkg/ip/utils_linux.go
Normal file
120
pkg/ip/utils_linux.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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"))
|
||||||
|
@ -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 := ¤t.Result{}
|
||||||
|
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SocketPathConf struct {
|
type SocketPathConf struct {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -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 := ¤t.Interface{}
|
ipvlan := ¤t.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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 := ¤t.Interface{}
|
macvlan := ¤t.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
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -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, ¤t.Interface{
|
result.Interfaces = append(result.Interfaces, ¤t.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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user