Compare commits

...

19 Commits

Author SHA1 Message Date
fa48f7515b ipam/dhcp: Add broadcast flag
Signed-off-by: Micah Hausler <hausler.m@gmail.com>
2021-02-05 16:38:32 +01:00
74a6b28a2c vendor: bump libcni
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2021-02-03 14:38:29 +01:00
8feef71fd3 add flannel to support dual stack ip
support three mode ip stack:
- only ipv4 stack
- only ipv6 stack
- dual stack ip

Signed-off-by: yaoice <yao3690093@gmail.com>
2021-01-27 17:18:02 +01:00
d1769ddd68 Merge pull request #575 from zhsj/fix-panic
bandwidth: fix panic in tests
2021-01-27 16:07:17 +00:00
343d233b4f bandwidth: fix panic in tests
Check error before using return value

Signed-off-by: Shengjing Zhu <zhsj@debian.org>
2021-01-25 01:13:27 +08:00
8c66d687c7 Merge pull request #565 from tlwr/main
DHCP timeout is configurable
2021-01-20 17:47:25 +01:00
49ee6f3768 Merge pull request #566 from crandles/fix-vlan-errors
vlan: fix error message text by removing ptp references
2021-01-20 10:44:39 -06:00
d41acb83c4 host-device: Add support for DPDK device (#490)
This commit would make host-device plugin as a placeholder
for DPDK device when applications wants to attach it with
a pod container through network attachment definition.

Signed-off-by: Periyasamy Palanisamy <periyasamy.palanisamy@est.tech>
2021-01-20 17:35:42 +01:00
7dc7a002cf Merge pull request #568 from containernetworking/jell/vlan-1
[main/vlan] Fix error handling for delegate IPAM plugin
2021-01-20 10:31:32 -06:00
075c5a0d67 [main/vlan] Fix error handling for delegate IPAM plugin
Signed-off-by: Piotr Skamruk <piotr.skamruk@gmail.com>
2021-01-14 15:52:19 +01:00
a8d1f5cd1b dhcp: default dhcp clien timeout is 10s
Consistent with ef524ad9cb/client.go (L39)

Signed-off-by: toby lorne <toby@toby.codes>
Co-authored-by: bruce ma <brucema19901024@gmail.com>
2021-01-13 19:24:41 +00:00
354def76b4 vlan: fix error message text by removing ptp references
Signed-off-by: Chris Randles <randles.chris@gmail.com>
2021-01-12 10:02:40 -05:00
25fc741e37 dhcp: daemon dhcp client timeout is configurable
Fixes #470

Signed-off-by: toby lorne <toby@toby.codes>
2021-01-11 22:13:54 +00:00
3161bb52e0 dhcp: timeout value is set in DHCP daemon
Eventually the timeout value will become a CLI argument

The default timeout was nestled all the way in the lease constructor

This commit is the first step in making the timeout configurable by
moving it to the DHCPLease constructor

Signed-off-by: toby lorne <toby@toby.codes>
2021-01-11 22:13:54 +00:00
48a97a7ad1 Merge pull request #564 from yuzhiquan/remove-unused-func
Remove unused function
2021-01-11 10:38:01 +00:00
ebce6d0b9b remove unused function
Signed-off-by: yuzhiquan <yuzhiquanlong@gmail.com>
2021-01-11 11:12:00 +08:00
3819ef7e86 Merge pull request #563 from tlwr/main
deps: bump coreos/go-iptables
2021-01-07 11:13:24 +00:00
691238ca57 deps: go mod tidy coreos/go-iptables
Signed-off-by: toby lorne <toby@toby.codes>
2021-01-06 16:13:33 +00:00
23a1d90e0b deps: bump coreos/go-iptables
Closes #544

The above issue describes a situation where using the bridge plugin
with IPv6 addresses prevented `DEL` from working correctly.

`DEL` seems to be failing in the body of `TeardownIPMasq`

This arises because:

* twice delete postrouting rules: `ipn.String()` `ipn.IP.String()` #279
* we are using a version of go-iptables which is bugged for v6

PR github.com/coreos/go-iptables/pull/74 describes why this does
not work. The error message is not being checked correctly.

Using a later version of go-iptables means that
* when the second `ipt.Delete` fails (this is okay)
* we will correctly interpret this as an non-fatal error
* `TeardownIPMasq` will not prematurely exit the method
* `ipt.ClearChain` now can run
* `ipt.DeleteChain` now can run

This explains why this was working for v4 but not v6

This commit was amended to include v0.5.0 instead of a pseudo-version
v0.4.6-0.20200318170312-12696f5c9108

Signed-off-by: toby lorne <toby@toby.codes>
2021-01-05 19:28:14 +00:00
16 changed files with 727 additions and 167 deletions

4
go.mod
View File

@ -7,8 +7,8 @@ require (
github.com/Microsoft/hcsshim v0.8.6
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/containernetworking/cni v0.8.0
github.com/coreos/go-iptables v0.4.5
github.com/containernetworking/cni v0.8.1
github.com/coreos/go-iptables v0.5.0
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0

8
go.sum
View File

@ -6,10 +6,10 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUq
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/containernetworking/cni v0.8.0 h1:BT9lpgGoH4jw3lFC7Odz2prU5ruiYKcgAjMCbgybcKI=
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI=
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6EjbQ=
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=

View File

@ -1,3 +1,17 @@
// Copyright 2021 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (

View File

@ -26,6 +26,7 @@ import (
"path/filepath"
"runtime"
"sync"
"time"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -41,11 +42,14 @@ type DHCP struct {
mux sync.Mutex
leases map[string]*DHCPLease
hostNetnsPrefix string
clientTimeout time.Duration
broadcast bool
}
func newDHCP() *DHCP {
func newDHCP(clientTimeout time.Duration) *DHCP {
return &DHCP{
leases: make(map[string]*DHCPLease),
leases: make(map[string]*DHCPLease),
clientTimeout: clientTimeout,
}
}
@ -63,7 +67,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
clientID := generateClientID(args.ContainerID, conf.Name, args.IfName)
hostNetns := d.hostNetnsPrefix + args.Netns
l, err := AcquireLease(clientID, hostNetns, args.IfName)
l, err := AcquireLease(clientID, hostNetns, args.IfName, d.clientTimeout, d.broadcast)
if err != nil {
return err
}
@ -156,7 +160,10 @@ func getListener(socketPath string) (net.Listener, error) {
}
}
func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
func runDaemon(
pidfilePath, hostPrefix, socketPath string,
dhcpClientTimeout time.Duration, broadcast bool,
) error {
// since other goroutines (on separate threads) will change namespaces,
// ensure the RPC server does not get scheduled onto those
runtime.LockOSThread()
@ -176,8 +183,9 @@ func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
return fmt.Errorf("Error getting listener: %v", err)
}
dhcp := newDHCP()
dhcp := newDHCP(dhcpClientTimeout)
dhcp.hostNetnsPrefix = hostPrefix
dhcp.broadcast = broadcast
rpc.Register(dhcp)
rpc.HandleHTTP()
http.Serve(l, nil)

View File

@ -56,6 +56,8 @@ type DHCPLease struct {
renewalTime time.Time
rebindingTime time.Time
expireTime time.Time
timeout time.Duration
broadcast bool
stopping uint32
stop chan struct{}
wg sync.WaitGroup
@ -64,11 +66,16 @@ type DHCPLease struct {
// AcquireLease gets an DHCP lease and then maintains it in the background
// by periodically renewing it. The acquired lease can be released by
// calling DHCPLease.Stop()
func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
func AcquireLease(
clientID, netns, ifName string,
timeout time.Duration, broadcast bool,
) (*DHCPLease, error) {
errCh := make(chan error, 1)
l := &DHCPLease{
clientID: clientID,
stop: make(chan struct{}),
clientID: clientID,
stop: make(chan struct{}),
timeout: timeout,
broadcast: broadcast,
}
log.Printf("%v: acquiring lease", clientID)
@ -115,7 +122,7 @@ func (l *DHCPLease) Stop() {
}
func (l *DHCPLease) acquire() error {
c, err := newDHCPClient(l.link, l.clientID)
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
if err != nil {
return err
}
@ -242,7 +249,7 @@ func (l *DHCPLease) downIface() {
}
func (l *DHCPLease) renew() error {
c, err := newDHCPClient(l.link, l.clientID)
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
if err != nil {
return err
}
@ -273,7 +280,7 @@ func (l *DHCPLease) renew() error {
func (l *DHCPLease) release() error {
log.Printf("%v: releasing lease", l.clientID)
c, err := newDHCPClient(l.link, l.clientID)
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
if err != nil {
return err
}
@ -361,7 +368,11 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
return nil, errNoMoreTries
}
func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, error) {
func newDHCPClient(
link netlink.Link, clientID string,
timeout time.Duration,
broadcast bool,
) (*dhcp4client.Client, error) {
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
if err != nil {
return nil, err
@ -369,8 +380,8 @@ func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, err
return dhcp4client.New(
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
dhcp4client.Timeout(5*time.Second),
dhcp4client.Broadcast(false),
dhcp4client.Timeout(timeout),
dhcp4client.Broadcast(broadcast),
dhcp4client.Connection(pktsock),
)
}

View File

@ -22,6 +22,7 @@ import (
"net/rpc"
"os"
"path/filepath"
"time"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -37,17 +38,21 @@ func main() {
var pidfilePath string
var hostPrefix string
var socketPath string
var broadcast bool
var timeout time.Duration
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root")
daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath")
daemonFlags.BoolVar(&broadcast, "broadcast", false, "broadcast DHCP leases")
daemonFlags.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration")
daemonFlags.Parse(os.Args[2:])
if socketPath == "" {
socketPath = defaultSocketPath
}
if err := runDaemon(pidfilePath, hostPrefix, socketPath); err != nil {
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, broadcast); err != nil {
log.Printf(err.Error())
os.Exit(1)
}

View File

@ -15,7 +15,6 @@
package main
import (
"encoding/json"
"fmt"
"net"
"strings"
@ -34,14 +33,6 @@ func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
}
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
n := &types.NetConf{}
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)

View File

@ -43,6 +43,9 @@ const (
sysBusPCI = "/sys/bus/pci/devices"
)
// Array of different linux drivers bound to network device needed for DPDK
var userspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"}
//NetConf for host-device config, look the README to learn how to use those parameters
type NetConf struct {
types.NetConf
@ -91,6 +94,16 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer containerNs.Close()
if len(cfg.PCIAddr) > 0 {
isDpdkMode, err := hasDpdkDriver(cfg.PCIAddr)
if err != nil {
return fmt.Errorf("error with host device: %v", err)
}
if isDpdkMode {
return types.PrintResult(&current.Result{}, cfg.CNIVersion)
}
}
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
if err != nil {
return fmt.Errorf("failed to find host device: %v", err)
@ -168,6 +181,16 @@ func cmdDel(args *skel.CmdArgs) error {
}
defer containerNs.Close()
if len(cfg.PCIAddr) > 0 {
isDpdkMode, err := hasDpdkDriver(cfg.PCIAddr)
if err != nil {
return fmt.Errorf("error with host device: %v", err)
}
if isDpdkMode {
return nil
}
}
if err := moveLinkOut(containerNs, args.IfName); err != nil {
return err
}
@ -255,6 +278,25 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
})
}
func hasDpdkDriver(pciaddr string) (bool, error) {
driverLink := filepath.Join(sysBusPCI, pciaddr, "driver")
driverPath, err := filepath.EvalSymlinks(driverLink)
if err != nil {
return false, err
}
driverStat, err := os.Stat(driverPath)
if err != nil {
return false, err
}
driverName := driverStat.Name()
for _, drv := range userspaceDrivers {
if driverName == drv {
return true, nil
}
}
return false, nil
}
func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error {
result := current.Result{
CNIVersion: current.ImplementedSpecVersion,

View File

@ -152,7 +152,7 @@ func cmdAdd(args *skel.CmdArgs) error {
// run the IPAM plugin and get back the config to apply
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
if err != nil {
return err
return fmt.Errorf("failed to execute IPAM delegate: %v", err)
}
// Invoke ipam del if err to avoid ip leak
@ -238,7 +238,7 @@ func cmdCheck(args *skel.CmdArgs) error {
return err
}
if conf.NetConf.RawPrevResult == nil {
return fmt.Errorf("ptp: Required prevResult missing")
return fmt.Errorf("vlan: Required prevResult missing")
}
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
return err
@ -308,10 +308,10 @@ func validateCniContainerInterface(intf current.Interface, masterIndex int, vlan
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
return fmt.Errorf("vlan: 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)
return fmt.Errorf("vlan: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
vlan, isVlan := link.(*netlink.Vlan)

View File

@ -936,6 +936,7 @@ var _ = Describe("bandwidth test", func() {
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(err).NotTo(HaveOccurred())
Expect(r.Print()).To(Succeed())
return err
@ -944,6 +945,7 @@ var _ = Describe("bandwidth test", func() {
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(err).NotTo(HaveOccurred())
Expect(r.Print()).To(Succeed())
return err
@ -1081,6 +1083,7 @@ var _ = Describe("bandwidth test", func() {
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(err).NotTo(HaveOccurred())
Expect(r.Print()).To(Succeed())
return err
@ -1089,6 +1092,7 @@ var _ = Describe("bandwidth test", func() {
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
Expect(err).NotTo(HaveOccurred())
Expect(r.Print()).To(Succeed())
return err

View File

@ -57,6 +57,8 @@ type NetConf struct {
type subnetEnv struct {
nw *net.IPNet
sn *net.IPNet
ip6Nw *net.IPNet
ip6Sn *net.IPNet
mtu *uint
ipmasq *bool
}
@ -64,11 +66,11 @@ type subnetEnv struct {
func (se *subnetEnv) missing() string {
m := []string{}
if se.nw == nil {
m = append(m, "FLANNEL_NETWORK")
if se.nw == nil && se.ip6Nw == nil {
m = append(m, []string{"FLANNEL_NETWORK", "FLANNEL_IPV6_NETWORK"}...)
}
if se.sn == nil {
m = append(m, "FLANNEL_SUBNET")
if se.sn == nil && se.ip6Sn == nil {
m = append(m, []string{"FLANNEL_SUBNET", "FLANNEL_IPV6_SUBNET"}...)
}
if se.mtu == nil {
m = append(m, "FLANNEL_MTU")
@ -128,6 +130,18 @@ func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
return nil, err
}
case "FLANNEL_IPV6_NETWORK":
_, se.ip6Nw, err = net.ParseCIDR(parts[1])
if err != nil {
return nil, err
}
case "FLANNEL_IPV6_SUBNET":
_, se.ip6Sn, err = net.ParseCIDR(parts[1])
if err != nil {
return nil, err
}
case "FLANNEL_MTU":
mtu, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {

View File

@ -40,13 +40,33 @@ func getDelegateIPAM(n *NetConf, fenv *subnetEnv) (map[string]interface{}, error
if !hasKey(ipam, "type") {
ipam["type"] = "host-local"
}
ipam["subnet"] = fenv.sn.String()
var rangesSlice [][]map[string]interface{}
if fenv.sn != nil && fenv.sn.String() != "" {
rangesSlice = append(rangesSlice, []map[string]interface{}{
{"subnet": fenv.sn.String()},
},
)
}
if fenv.ip6Sn != nil && fenv.ip6Sn.String() != "" {
rangesSlice = append(rangesSlice, []map[string]interface{}{
{"subnet": fenv.ip6Sn.String()},
},
)
}
ipam["ranges"] = rangesSlice
rtes, err := getIPAMRoutes(n)
if err != nil {
return nil, fmt.Errorf("failed to read IPAM routes: %w", err)
}
rtes = append(rtes, types.Route{Dst: *fenv.nw})
if fenv.nw != nil {
rtes = append(rtes, types.Route{Dst: *fenv.nw})
}
if fenv.ip6Nw != nil {
rtes = append(rtes, types.Route{Dst: *fenv.ip6Nw})
}
ipam["routes"] = rtes
return ipam, nil

View File

@ -30,10 +30,14 @@ import (
var _ = Describe("Flannel", func() {
var (
originalNS ns.NetNS
input string
subnetFile string
dataDir string
originalNS ns.NetNS
onlyIpv4Input string
onlyIpv6Input string
dualStackInput string
onlyIpv4SubnetFile string
onlyIpv6SubnetFile string
dualStackSubnetFile string
dataDir string
)
BeforeEach(func() {
@ -48,10 +52,10 @@ var _ = Describe("Flannel", func() {
const inputTemplate = `
{
"name": "cni-flannel",
"type": "flannel",
"subnetFile": "%s",
"dataDir": "%s"%s
"name": "cni-flannel",
"type": "flannel",
"subnetFile": "%s",
"dataDir": "%s"%s
}`
const inputIPAMTemplate = `
@ -70,11 +74,25 @@ var _ = Describe("Flannel", func() {
{ "dst": "10.96.0.0/12" },
{ "dst": "192.168.244.0/24", "gw": "10.1.17.20" }`
const flannelSubnetEnv = `
const onlyIpv4FlannelSubnetEnv = `
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.17.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true
`
const onlyIpv6FlannelSubnetEnv = `
FLANNEL_IPV6_NETWORK=fc00::/48
FLANNEL_IPV6_SUBNET=fc00::1/64
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true
`
const dualStackFlannelSubnetEnv = `
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.17.1/24
FLANNEL_IPV6_NETWORK=fc00::/48
FLANNEL_IPV6_SUBNET=fc00::1/64
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true
`
var writeSubnetEnv = func(contents string) string {
@ -96,7 +114,7 @@ FLANNEL_IPMASQ=true
return c
}
var makeInput = func(inputIPAM string) string {
var makeInput = func(inputIPAM string, subnetFile string) string {
ipamPart := ""
if len(inputIPAM) > 0 {
ipamPart = ",\n \"ipam\":\n" + inputIPAM
@ -108,116 +126,350 @@ FLANNEL_IPMASQ=true
BeforeEach(func() {
var err error
// flannel subnet.env
subnetFile = writeSubnetEnv(flannelSubnetEnv)
onlyIpv4SubnetFile = writeSubnetEnv(onlyIpv4FlannelSubnetEnv)
onlyIpv6SubnetFile = writeSubnetEnv(onlyIpv6FlannelSubnetEnv)
dualStackSubnetFile = writeSubnetEnv(dualStackFlannelSubnetEnv)
// flannel state dir
dataDir, err = ioutil.TempDir("", "dataDir")
Expect(err).NotTo(HaveOccurred())
input = makeInput("")
onlyIpv4Input = makeInput("", onlyIpv4SubnetFile)
onlyIpv6Input = makeInput("", onlyIpv6SubnetFile)
dualStackInput = makeInput("", dualStackSubnetFile)
})
AfterEach(func() {
os.Remove(subnetFile)
os.Remove(onlyIpv4SubnetFile)
os.Remove(onlyIpv6SubnetFile)
os.Remove(dualStackSubnetFile)
os.Remove(dataDir)
})
Describe("CNI lifecycle", func() {
It("uses dataDir for storing network configuration", func() {
const IFNAME = "eth0"
Context("when using only ipv4 stack", func() {
It("uses dataDir for storing network configuration with ipv4 stack", func() {
const IFNAME = "eth0"
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "some-container-id",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(input),
}
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
By("calling ADD")
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
By("check that plugin writes the net config to dataDir")
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id")
Expect(path).Should(BeAnExistingFile())
args := &skel.CmdArgs{
ContainerID: "some-container-id-ipv4",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(onlyIpv4Input),
}
netConfBytes, err := ioutil.ReadFile(path)
Expect(err).NotTo(HaveOccurred())
expected := `{
"ipMasq" : false,
"ipam" : {
"routes" : [
{
"dst" : "10.1.0.0/16"
}
],
"subnet" : "10.1.17.0/24",
"type" : "host-local"
},
"isGateway": true,
"mtu" : 1472,
"name" : "cni-flannel",
"type" : "bridge"
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
By("calling ADD with ipv4 stack")
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin writes the net config to dataDir with ipv4 stack")
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-ipv4")
Expect(path).Should(BeAnExistingFile())
netConfBytes, err := ioutil.ReadFile(path)
Expect(err).NotTo(HaveOccurred())
expected := `{
"ipMasq": false,
"ipam": {
"routes": [
{
"dst": "10.1.0.0/16"
}
],
"ranges": [
[{
"subnet": "10.1.17.0/24"
}]
],
"type": "host-local"
},
"isGateway": true,
"mtu": 1472,
"name": "cni-flannel",
"type": "bridge"
}
`
Expect(netConfBytes).Should(MatchJSON(expected))
Expect(netConfBytes).Should(MatchJSON(expected))
result, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).To(HaveLen(1))
result, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).To(HaveLen(1))
By("calling DEL")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
By("calling DEL with ipv4 stack")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin removes net config from state dir with ipv4 stack")
Expect(path).ShouldNot(BeAnExistingFile())
By("calling DEL again with ipv4 stack")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
By("check that plugin does not fail due to missing net config with ipv4 stack")
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})
Context("when using only ipv6 stack", func() {
It("uses dataDir for storing network configuration with ipv6 stack", func() {
const IFNAME = "eth0"
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "some-container-id-ipv6",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(onlyIpv6Input),
}
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
By("calling ADD with ipv6 stack")
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin writes the net config to dataDir with ipv6 stack")
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-ipv6")
Expect(path).Should(BeAnExistingFile())
netConfBytes, err := ioutil.ReadFile(path)
Expect(err).NotTo(HaveOccurred())
expected := `{
"ipMasq": false,
"ipam": {
"routes": [
{
"dst": "fc00::/48"
}
],
"ranges": [
[{
"subnet": "fc00::/64"
}]
],
"type": "host-local"
},
"isGateway": true,
"mtu": 1472,
"name": "cni-flannel",
"type": "bridge"
}
`
Expect(netConfBytes).Should(MatchJSON(expected))
result, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).To(HaveLen(1))
By("calling DEL with ipv6 stack")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin removes net config from state dir with ipv6 stack")
Expect(path).ShouldNot(BeAnExistingFile())
By("calling DEL again with ipv6 stack")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
By("check that plugin does not fail due to missing net config with ipv6 stack")
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})
Context("when using dual stack", func() {
It("uses dataDir for storing network configuration with dual stack", func() {
const IFNAME = "eth0"
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "some-container-id-dual-stack",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(dualStackInput),
}
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
By("calling ADD with dual stack")
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin writes the net config to dataDir with dual stack")
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-dual-stack")
Expect(path).Should(BeAnExistingFile())
netConfBytes, err := ioutil.ReadFile(path)
Expect(err).NotTo(HaveOccurred())
expected := `{
"ipMasq": false,
"ipam": {
"routes": [
{
"dst": "10.1.0.0/16"
},
{
"dst": "fc00::/48"
}
],
"ranges": [
[{
"subnet": "10.1.17.0/24"
}],
[{
"subnet": "fc00::/64"
}]
],
"type": "host-local"
},
"isGateway": true,
"mtu": 1472,
"name": "cni-flannel",
"type": "bridge"
}
`
Expect(netConfBytes).Should(MatchJSON(expected))
result, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).To(HaveLen(2))
By("calling DEL with dual stack")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin removes net config from state dir with dual stack")
Expect(path).ShouldNot(BeAnExistingFile())
By("calling DEL again with dual stack")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
By("check that plugin does not fail due to missing net config with dual stack")
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin removes net config from state dir")
Expect(path).ShouldNot(BeAnExistingFile())
By("calling DEL again")
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
By("check that plugin does not fail due to missing net config")
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})
Describe("loadFlannelNetConf", func() {
Context("when subnetFile and dataDir are specified", func() {
It("loads flannel network config", func() {
conf, err := loadFlannelNetConf([]byte(input))
Context("when subnetFile and dataDir are specified with ipv4 stack", func() {
It("loads flannel network config with ipv4 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.Name).To(Equal("cni-flannel"))
Expect(conf.Type).To(Equal("flannel"))
Expect(conf.SubnetFile).To(Equal(subnetFile))
Expect(conf.SubnetFile).To(Equal(onlyIpv4SubnetFile))
Expect(conf.DataDir).To(Equal(dataDir))
})
})
Context("when defaulting subnetFile and dataDir", func() {
Context("when subnetFile and dataDir are specified with ipv6 stack", func() {
It("loads flannel network config with ipv6 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.Name).To(Equal("cni-flannel"))
Expect(conf.Type).To(Equal("flannel"))
Expect(conf.SubnetFile).To(Equal(onlyIpv6SubnetFile))
Expect(conf.DataDir).To(Equal(dataDir))
})
})
Context("when subnetFile and dataDir are specified with dual stack", func() {
It("loads flannel network config with dual stack", func() {
conf, err := loadFlannelNetConf([]byte(dualStackInput))
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.Name).To(Equal("cni-flannel"))
Expect(conf.Type).To(Equal("flannel"))
Expect(conf.SubnetFile).To(Equal(dualStackSubnetFile))
Expect(conf.DataDir).To(Equal(dataDir))
})
})
Context("when defaulting subnetFile and dataDir with ipv4 stack", func() {
BeforeEach(func() {
input = `{
onlyIpv4Input = `{
"name": "cni-flannel",
"type": "flannel"
}`
})
It("loads flannel network config with defaults", func() {
conf, err := loadFlannelNetConf([]byte(input))
It("loads flannel network config with defaults with ipv4 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.Name).To(Equal("cni-flannel"))
Expect(conf.Type).To(Equal("flannel"))
Expect(conf.SubnetFile).To(Equal(defaultSubnetFile))
Expect(conf.DataDir).To(Equal(defaultDataDir))
})
})
Context("when defaulting subnetFile and dataDir with ipv6 stack", func() {
BeforeEach(func() {
onlyIpv6Input = `{
"name": "cni-flannel",
"type": "flannel"
}`
})
It("loads flannel network config with defaults with ipv6 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.Name).To(Equal("cni-flannel"))
Expect(conf.Type).To(Equal("flannel"))
Expect(conf.SubnetFile).To(Equal(defaultSubnetFile))
Expect(conf.DataDir).To(Equal(defaultDataDir))
})
})
Context("when defaulting subnetFile and dataDir with dual stack", func() {
BeforeEach(func() {
dualStackInput = `{
"name": "cni-flannel",
"type": "flannel"
}`
})
It("loads flannel network config with defaults with dual stack", func() {
conf, err := loadFlannelNetConf([]byte(dualStackInput))
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.Name).To(Equal("cni-flannel"))
Expect(conf.Type).To(Equal("flannel"))
@ -227,9 +479,9 @@ FLANNEL_IPMASQ=true
})
Describe("loadFlannelSubnetEnv", func() {
Context("when flannel subnet env is valid", func() {
It("loads flannel subnet config", func() {
conf, err := loadFlannelSubnetEnv(subnetFile)
Context("when flannel subnet env is valid with ipv4 stack", func() {
It("loads flannel subnet config with ipv4 stack", func() {
conf, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.nw.String()).To(Equal("10.1.0.0/16"))
Expect(conf.sn.String()).To(Equal("10.1.17.0/24"))
@ -239,56 +491,188 @@ FLANNEL_IPMASQ=true
})
})
Context("when flannel subnet env is invalid", func() {
Context("when flannel subnet env is valid with ipv6 stack", func() {
It("loads flannel subnet config with ipv6 stack", func() {
conf, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.ip6Nw.String()).To(Equal("fc00::/48"))
Expect(conf.ip6Sn.String()).To(Equal("fc00::/64"))
var mtu uint = 1472
Expect(*conf.mtu).To(Equal(mtu))
Expect(*conf.ipmasq).To(BeTrue())
})
})
Context("when flannel subnet env is valid with dual stack", func() {
It("loads flannel subnet config with dual stack", func() {
conf, err := loadFlannelSubnetEnv(dualStackSubnetFile)
Expect(err).ShouldNot(HaveOccurred())
Expect(conf.nw.String()).To(Equal("10.1.0.0/16"))
Expect(conf.sn.String()).To(Equal("10.1.17.0/24"))
Expect(conf.ip6Nw.String()).To(Equal("fc00::/48"))
Expect(conf.ip6Sn.String()).To(Equal("fc00::/64"))
var mtu uint = 1472
Expect(*conf.mtu).To(Equal(mtu))
Expect(*conf.ipmasq).To(BeTrue())
})
})
Context("when flannel subnet env is invalid with ipv4 stack", func() {
BeforeEach(func() {
subnetFile = writeSubnetEnv("foo=bar")
onlyIpv4SubnetFile = writeSubnetEnv("foo=bar")
})
It("returns an error", func() {
_, err := loadFlannelSubnetEnv(subnetFile)
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
_, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
})
})
Context("when flannel subnet env is invalid with ipv6 stack", func() {
BeforeEach(func() {
onlyIpv6SubnetFile = writeSubnetEnv("foo=bar")
})
It("returns an error", func() {
_, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
})
})
Context("when flannel subnet env is invalid with dual stack", func() {
BeforeEach(func() {
dualStackSubnetFile = writeSubnetEnv("foo=bar")
})
It("returns an error", func() {
_, err := loadFlannelSubnetEnv(dualStackSubnetFile)
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
})
})
})
})
Describe("getDelegateIPAM", func() {
Context("when input IPAM is provided", func() {
Context("when input IPAM is provided with ipv4 stack", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
input = makeInput(inputIPAM)
onlyIpv4Input = makeInput(inputIPAM, onlyIpv4SubnetFile)
})
It("configures Delegate IPAM accordingly", func() {
conf, err := loadFlannelNetConf([]byte(input))
It("configures Delegate IPAM accordingly with ipv4 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(subnetFile)
fenv, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
subnet := "\"subnet\": \"10.1.17.0/24\""
subnet := "\"ranges\": [[{\"subnet\": \"10.1.17.0/24\"}]]"
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided without 'type'", func() {
Context("when input IPAM is provided with ipv6 stack", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
onlyIpv6Input = makeInput(inputIPAM, onlyIpv6SubnetFile)
})
It("configures Delegate IPAM accordingly with ipv6 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"fc00::/48\" }\n"
subnet := "\"ranges\": [[{ \"subnet\": \"fc00::/64\" }]]"
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided with dual stack", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
dualStackInput = makeInput(inputIPAM, dualStackSubnetFile)
})
It("configures Delegate IPAM accordingly with dual stack", func() {
conf, err := loadFlannelNetConf([]byte(dualStackInput))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(dualStackSubnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }" + ",\n" + "{ \"dst\": \"fc00::/48\" }\n"
subnet := "\"ranges\": [[{ \"subnet\": \"10.1.17.0/24\" }],\n[{ \"subnet\": \"fc00::/64\" }]]"
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided without 'type' with ipv4 stack", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
input = makeInput(inputIPAM)
onlyIpv4Input = makeInput(inputIPAM, onlyIpv4SubnetFile)
})
It("configures Delegate IPAM with 'host-local' ipam", func() {
conf, err := loadFlannelNetConf([]byte(input))
It("configures Delegate IPAM with 'host-local' ipam with ipv4 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(subnetFile)
fenv, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
subnet := "\"subnet\": \"10.1.17.0/24\""
subnet := "\"ranges\": [[{\"subnet\": \"10.1.17.0/24\"}]]"
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided without 'type' with ipv6 stack", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
onlyIpv6Input = makeInput(inputIPAM, onlyIpv6SubnetFile)
})
It("configures Delegate IPAM with 'host-local' ipam with ipv6 stack", func() {
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"fc00::/48\" }\n"
subnet := "\"ranges\": [[{ \"subnet\": \"fc00::/64\" }]]"
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided without 'type' with dual stack", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
dualStackInput = makeInput(inputIPAM, dualStackSubnetFile)
})
It("configures Delegate IPAM with 'host-local' ipam with dual stack", func() {
conf, err := loadFlannelNetConf([]byte(dualStackInput))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(dualStackSubnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }" + ",\n" + "{ \"dst\": \"fc00::/48\" }\n"
subnet := "\"ranges\": [[{ \"subnet\": \"10.1.17.0/24\" }],\n[{ \"subnet\": \"fc00::/64\" }]]"
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))

View File

@ -18,6 +18,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
)
// FindInPath returns the full path of the plugin by searching in the provided path
@ -26,6 +27,10 @@ func FindInPath(plugin string, paths []string) (string, error) {
return "", fmt.Errorf("no plugin name provided")
}
if strings.ContainsRune(plugin, os.PathSeparator) {
return "", fmt.Errorf("invalid plugin name: %s", plugin)
}
if len(paths) == 0 {
return "", fmt.Errorf("no paths provided")
}

View File

@ -31,7 +31,6 @@ type Error struct {
exec.ExitError
cmd exec.Cmd
msg string
proto Protocol
exitStatus *int //for overriding
}
@ -51,9 +50,8 @@ func (e *Error) IsNotExist() bool {
if e.ExitStatus() != 1 {
return false
}
cmdIptables := getIptablesCommand(e.proto)
msgNoRuleExist := fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", cmdIptables)
msgNoChainExist := fmt.Sprintf("%s: No chain/target/match by that name.\n", cmdIptables)
msgNoRuleExist := "Bad rule (does a matching rule exist in that chain?).\n"
msgNoChainExist := "No chain/target/match by that name.\n"
return strings.Contains(e.msg, msgNoRuleExist) || strings.Contains(e.msg, msgNoChainExist)
}
@ -75,6 +73,7 @@ type IPTables struct {
v2 int
v3 int
mode string // the underlying iptables operating mode, e.g. nf_tables
timeout int // time to wait for the iptables lock, default waits forever
}
// Stat represents a structured statistic entry.
@ -91,19 +90,42 @@ type Stat struct {
Options string `json:"options"`
}
// New creates a new IPTables.
// For backwards compatibility, this always uses IPv4, i.e. "iptables".
func New() (*IPTables, error) {
return NewWithProtocol(ProtocolIPv4)
type option func(*IPTables)
func IPFamily(proto Protocol) option {
return func(ipt *IPTables) {
ipt.proto = proto
}
}
// New creates a new IPTables for the given proto.
// The proto will determine which command is used, either "iptables" or "ip6tables".
func NewWithProtocol(proto Protocol) (*IPTables, error) {
path, err := exec.LookPath(getIptablesCommand(proto))
func Timeout(timeout int) option {
return func(ipt *IPTables) {
ipt.timeout = timeout
}
}
// New creates a new IPTables configured with the options passed as parameter.
// For backwards compatibility, by default always uses IPv4 and timeout 0.
// i.e. you can create an IPv6 IPTables using a timeout of 5 seconds passing
// the IPFamily and Timeout options as follow:
// ip6t := New(IPFamily(ProtocolIPv6), Timeout(5))
func New(opts ...option) (*IPTables, error) {
ipt := &IPTables{
proto: ProtocolIPv4,
timeout: 0,
}
for _, opt := range opts {
opt(ipt)
}
path, err := exec.LookPath(getIptablesCommand(ipt.proto))
if err != nil {
return nil, err
}
ipt.path = path
vstring, err := getIptablesVersionString(path)
if err != nil {
return nil, fmt.Errorf("could not get iptables version: %v", err)
@ -112,21 +134,23 @@ func NewWithProtocol(proto Protocol) (*IPTables, error) {
if err != nil {
return nil, fmt.Errorf("failed to extract iptables version from [%s]: %v", vstring, err)
}
ipt.v1 = v1
ipt.v2 = v2
ipt.v3 = v3
ipt.mode = mode
checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)
ipt.hasCheck = checkPresent
ipt.hasWait = waitPresent
ipt.hasRandomFully = randomFullyPresent
ipt := IPTables{
path: path,
proto: proto,
hasCheck: checkPresent,
hasWait: waitPresent,
hasRandomFully: randomFullyPresent,
v1: v1,
v2: v2,
v3: v3,
mode: mode,
}
return &ipt, nil
return ipt, nil
}
// New creates a new IPTables for the given proto.
// The proto will determine which command is used, either "iptables" or "ip6tables".
func NewWithProtocol(proto Protocol) (*IPTables, error) {
return New(IPFamily(proto), Timeout(0))
}
// Proto returns the protocol used by this IPTables.
@ -185,6 +209,14 @@ func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error {
return ipt.run(cmd...)
}
func (ipt *IPTables) DeleteIfExists(table, chain string, rulespec ...string) error {
exists, err := ipt.Exists(table, chain, rulespec...)
if err == nil && exists {
err = ipt.Delete(table, chain, rulespec...)
}
return err
}
// List rules in specified table/chain
func (ipt *IPTables) List(table, chain string) ([]string, error) {
args := []string{"-t", table, "-S", chain}
@ -222,6 +254,21 @@ func (ipt *IPTables) ListChains(table string) ([]string, error) {
return chains, nil
}
// '-S' is fine with non existing rule index as long as the chain exists
// therefore pass index 1 to reduce overhead for large chains
func (ipt *IPTables) ChainExists(table, chain string) (bool, error) {
err := ipt.run("-t", table, "-S", chain, "1")
eerr, eok := err.(*Error)
switch {
case err == nil:
return true, nil
case eok && eerr.ExitStatus() == 1:
return false, nil
default:
return false, err
}
}
// Stats lists rules including the byte and packet counts
func (ipt *IPTables) Stats(table, chain string) ([][]string, error) {
args := []string{"-t", table, "-L", chain, "-n", "-v", "-x"}
@ -401,6 +448,18 @@ func (ipt *IPTables) DeleteChain(table, chain string) error {
return ipt.run("-t", table, "-X", chain)
}
func (ipt *IPTables) ClearAndDeleteChain(table, chain string) error {
exists, err := ipt.ChainExists(table, chain)
if err != nil || !exists {
return err
}
err = ipt.run("-t", table, "-F", chain)
if err == nil {
err = ipt.run("-t", table, "-X", chain)
}
return err
}
// ChangePolicy changes policy on chain to target
func (ipt *IPTables) ChangePolicy(table, chain, target string) error {
return ipt.run("-t", table, "-P", chain, target)
@ -428,6 +487,9 @@ func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
args = append([]string{ipt.path}, args...)
if ipt.hasWait {
args = append(args, "--wait")
if ipt.timeout != 0 {
args = append(args, strconv.Itoa(ipt.timeout))
}
} else {
fmu, err := newXtablesFileLock()
if err != nil {
@ -452,7 +514,7 @@ func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
if err := cmd.Run(); err != nil {
switch e := err.(type) {
case *exec.ExitError:
return &Error{*e, cmd, stderr.String(), ipt.proto, nil}
return &Error{*e, cmd, stderr.String(), nil}
default:
return err
}

4
vendor/modules.txt vendored
View File

@ -28,7 +28,7 @@ github.com/alexflint/go-filemutex
# github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
## explicit
github.com/buger/jsonparser
# github.com/containernetworking/cni v0.8.0
# github.com/containernetworking/cni v0.8.1
## explicit
github.com/containernetworking/cni/libcni
github.com/containernetworking/cni/pkg/invoke
@ -38,7 +38,7 @@ github.com/containernetworking/cni/pkg/types/020
github.com/containernetworking/cni/pkg/types/current
github.com/containernetworking/cni/pkg/utils
github.com/containernetworking/cni/pkg/version
# github.com/coreos/go-iptables v0.4.5
# github.com/coreos/go-iptables v0.5.0
## explicit
github.com/coreos/go-iptables/iptables
# github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7