Revision eeb3e8cd5e0d6e269575b51e2b8c462503c12c0b authored by Tom Hadlaw on 20 July 2024, 19:48:55 UTC, committed by Tom Hadlaw on 20 July 2024, 19:48:55 UTC
Signed-off-by: Tom Hadlaw <tom.hadlaw@isovalent.com>
1 parent acc80d2
config_test.go
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package option
import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cilium/cilium/pkg/cidr"
"github.com/cilium/cilium/pkg/defaults"
ipamOption "github.com/cilium/cilium/pkg/ipam/option"
"github.com/cilium/cilium/pkg/lock"
)
func TestValidateIPv6ClusterAllocCIDR(t *testing.T) {
valid1 := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPv6ClusterAllocCIDR: "fdfd::/64",
}
require.Nil(t, valid1.validateIPv6ClusterAllocCIDR())
require.Equal(t, "fdfd::", valid1.IPv6ClusterAllocCIDRBase)
valid2 := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPv6ClusterAllocCIDR: "fdfd:fdfd:fdfd:fdfd:aaaa::/64",
}
require.Nil(t, valid2.validateIPv6ClusterAllocCIDR())
require.Equal(t, "fdfd:fdfd:fdfd:fdfd::", valid2.IPv6ClusterAllocCIDRBase)
invalid1 := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPv6ClusterAllocCIDR: "foo",
}
require.NotNil(t, invalid1.validateIPv6ClusterAllocCIDR())
invalid2 := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPv6ClusterAllocCIDR: "fdfd",
}
require.NotNil(t, invalid2.validateIPv6ClusterAllocCIDR())
invalid3 := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPv6ClusterAllocCIDR: "fdfd::/32",
}
require.NotNil(t, invalid3.validateIPv6ClusterAllocCIDR())
invalid4 := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
}
require.NotNil(t, invalid4.validateIPv6ClusterAllocCIDR())
}
func TestGetEnvName(t *testing.T) {
type args struct {
option string
}
tests := []struct {
name string
args args
want string
}{
{
name: "Normal option",
args: args{
option: "foo",
},
want: "CILIUM_FOO",
},
{
name: "Capital option",
args: args{
option: "FOO",
},
want: "CILIUM_FOO",
},
{
name: "with numbers",
args: args{
option: "2222",
},
want: "CILIUM_2222",
},
{
name: "mix numbers small letters",
args: args{
option: "22ada22",
},
want: "CILIUM_22ADA22",
},
{
name: "mix numbers small letters and dashes",
args: args{
option: "22ada2------2",
},
want: "CILIUM_22ADA2______2",
},
{
name: "normal option",
args: args{
option: "conntrack-garbage-collector-interval",
},
want: "CILIUM_CONNTRACK_GARBAGE_COLLECTOR_INTERVAL",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getEnvName(tt.args.option); got != tt.want {
t.Errorf("getEnvName() = %v, want %v", got, tt.want)
}
})
}
}
func TestReadDirConfig(t *testing.T) {
vp := viper.New()
var dirName string
type args struct {
dirName string
}
type want struct {
allSettings map[string]interface{}
err error
}
tests := []struct {
name string
setupArgs func() args
setupWant func() want
preTestRun func()
postTestRun func()
}{
{
name: "empty configuration",
preTestRun: func() {
dirName = t.TempDir()
fs := flag.NewFlagSet("empty configuration", flag.ContinueOnError)
vp.BindPFlags(fs)
},
setupArgs: func() args {
return args{
dirName: dirName,
}
},
setupWant: func() want {
return want{
allSettings: map[string]interface{}{},
err: nil,
}
},
postTestRun: func() {
os.RemoveAll(dirName)
},
},
{
name: "single file configuration",
preTestRun: func() {
dirName = t.TempDir()
fullPath := filepath.Join(dirName, "test")
err := os.WriteFile(fullPath, []byte(`"1"
`), os.FileMode(0644))
require.NoError(t, err)
fs := flag.NewFlagSet("single file configuration", flag.ContinueOnError)
fs.String("test", "", "")
BindEnv(vp, "test")
vp.BindPFlags(fs)
fmt.Println(fullPath)
},
setupArgs: func() args {
return args{
dirName: dirName,
}
},
setupWant: func() want {
return want{
allSettings: map[string]interface{}{"test": `"1"`},
err: nil,
}
},
postTestRun: func() {
os.RemoveAll(dirName)
},
},
}
for _, tt := range tests {
tt.preTestRun()
args := tt.setupArgs()
want := tt.setupWant()
m, err := ReadDirConfig(args.dirName)
require.Equal(t, want.err, err, fmt.Sprintf("Test Name: %s", tt.name))
err = MergeConfig(vp, m)
require.NoError(t, err)
assert.Equal(t, vp.AllSettings(), want.allSettings, fmt.Sprintf("Test Name: %s", tt.name))
tt.postTestRun()
}
}
func TestBindEnv(t *testing.T) {
vp := viper.New()
optName1 := "foo-bar"
os.Setenv("LEGACY_FOO_BAR", "legacy")
os.Setenv(getEnvName(optName1), "new")
BindEnvWithLegacyEnvFallback(vp, optName1, "LEGACY_FOO_BAR")
require.Equal(t, "new", vp.GetString(optName1))
optName2 := "bar-foo"
BindEnvWithLegacyEnvFallback(vp, optName2, "LEGACY_FOO_BAR")
require.Equal(t, "legacy", vp.GetString(optName2))
}
func TestEnabledFunctions(t *testing.T) {
d := &DaemonConfig{ConfigPatchMutex: new(lock.RWMutex)}
assert.False(t, d.IPv4Enabled())
assert.False(t, d.IPv6Enabled())
assert.False(t, d.SCTPEnabled())
d = &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4: true,
}
assert.True(t, d.IPv4Enabled())
assert.False(t, d.IPv6Enabled())
assert.False(t, d.SCTPEnabled())
d = &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv6: true,
}
assert.False(t, d.IPv4Enabled())
assert.True(t, d.IPv6Enabled())
assert.False(t, d.SCTPEnabled())
d = &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableSCTP: true,
}
assert.False(t, d.IPv4Enabled())
assert.False(t, d.IPv6Enabled())
assert.True(t, d.SCTPEnabled())
d = &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
}
require.Empty(t, d.IPAMMode())
d = &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMENI,
}
require.Equal(t, ipamOption.IPAMENI, d.IPAMMode())
}
func TestLocalAddressExclusion(t *testing.T) {
d := &DaemonConfig{ConfigPatchMutex: new(lock.RWMutex)}
err := d.parseExcludedLocalAddresses([]string{"1.1.1.1/32", "3.3.3.0/24", "f00d::1/128"})
require.NoError(t, err)
require.True(t, d.IsExcludedLocalAddress(net.ParseIP("1.1.1.1")))
require.False(t, d.IsExcludedLocalAddress(net.ParseIP("1.1.1.2")))
require.True(t, d.IsExcludedLocalAddress(net.ParseIP("3.3.3.1")))
require.True(t, d.IsExcludedLocalAddress(net.ParseIP("f00d::1")))
require.False(t, d.IsExcludedLocalAddress(net.ParseIP("f00d::2")))
}
func TestCheckMapSizeLimits(t *testing.T) {
type sizes struct {
AuthMapEntries int
CTMapEntriesGlobalTCP int
CTMapEntriesGlobalAny int
NATMapEntriesGlobal int
PolicyMapEntries int
LBMapEntries int
FragmentsMapEntries int
NeighMapEntriesGlobal int
SockRevNatEntries int
WantErr bool
}
tests := []struct {
name string
d *DaemonConfig
want sizes
}{
{
name: "default map sizes",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
AuthMapEntries: AuthMapEntriesDefault,
CTMapEntriesGlobalTCP: CTMapEntriesGlobalTCPDefault,
CTMapEntriesGlobalAny: CTMapEntriesGlobalAnyDefault,
NATMapEntriesGlobal: NATMapEntriesGlobalDefault,
PolicyMapEntries: 16384,
LBMapEntries: 65536,
FragmentsMapEntries: defaults.FragmentsMapEntries,
NeighMapEntriesGlobal: NATMapEntriesGlobalDefault,
SockRevNatEntries: SockRevNATMapEntriesDefault,
},
want: sizes{
AuthMapEntries: AuthMapEntriesDefault,
CTMapEntriesGlobalTCP: CTMapEntriesGlobalTCPDefault,
CTMapEntriesGlobalAny: CTMapEntriesGlobalAnyDefault,
NATMapEntriesGlobal: NATMapEntriesGlobalDefault,
PolicyMapEntries: 16384,
LBMapEntries: 65536,
FragmentsMapEntries: defaults.FragmentsMapEntries,
NeighMapEntriesGlobal: NATMapEntriesGlobalDefault,
SockRevNatEntries: SockRevNATMapEntriesDefault,
WantErr: false,
},
},
{
name: "arbitrary map sizes within range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
AuthMapEntries: 20000,
CTMapEntriesGlobalTCP: 20000,
CTMapEntriesGlobalAny: 18000,
NATMapEntriesGlobal: 2048,
PolicyMapEntries: 512,
LBMapEntries: 1 << 14,
SockRevNatEntries: 18000,
FragmentsMapEntries: 2 << 14,
},
want: sizes{
AuthMapEntries: 20000,
CTMapEntriesGlobalTCP: 20000,
CTMapEntriesGlobalAny: 18000,
NATMapEntriesGlobal: 2048,
PolicyMapEntries: 512,
LBMapEntries: 1 << 14,
SockRevNatEntries: 18000,
FragmentsMapEntries: 2 << 14,
WantErr: false,
},
},
{
name: "Auth map size below range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
AuthMapEntries: AuthMapEntriesMin - 1,
},
want: sizes{
AuthMapEntries: AuthMapEntriesMin - 1,
WantErr: true,
},
},
{
name: "Auth map size above range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
AuthMapEntries: AuthMapEntriesMax + 1,
},
want: sizes{
AuthMapEntries: AuthMapEntriesMax + 1,
WantErr: true,
},
},
{
name: "CT TCP map size below range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
CTMapEntriesGlobalTCP: LimitTableMin - 1,
},
want: sizes{
CTMapEntriesGlobalTCP: LimitTableMin - 1,
WantErr: true,
},
},
{
name: "CT TCP map size above range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
CTMapEntriesGlobalTCP: LimitTableMax + 1,
},
want: sizes{
CTMapEntriesGlobalTCP: LimitTableMax + 1,
WantErr: true,
},
},
{
name: "CT Any map size below range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
CTMapEntriesGlobalAny: LimitTableMin - 1,
},
want: sizes{
CTMapEntriesGlobalAny: LimitTableMin - 1,
WantErr: true,
},
},
{
name: "CT Any map size above range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
CTMapEntriesGlobalAny: LimitTableMax + 1,
},
want: sizes{
CTMapEntriesGlobalAny: LimitTableMax + 1,
WantErr: true,
},
},
{
name: "NAT map size below range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
NATMapEntriesGlobal: LimitTableMin - 1,
},
want: sizes{
NATMapEntriesGlobal: LimitTableMin - 1,
WantErr: true,
},
},
{
name: "NAT map size above range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
NATMapEntriesGlobal: LimitTableMax + 1,
},
want: sizes{
NATMapEntriesGlobal: LimitTableMax + 1,
WantErr: true,
},
},
{
name: "NAT map auto sizing with default size",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
AuthMapEntries: AuthMapEntriesDefault,
CTMapEntriesGlobalTCP: 2048,
CTMapEntriesGlobalAny: 4096,
NATMapEntriesGlobal: NATMapEntriesGlobalDefault,
SockRevNatEntries: 4096,
PolicyMapEntries: 16384,
LBMapEntries: 65536,
FragmentsMapEntries: defaults.FragmentsMapEntries,
},
want: sizes{
AuthMapEntries: AuthMapEntriesDefault,
CTMapEntriesGlobalTCP: 2048,
CTMapEntriesGlobalAny: 4096,
NATMapEntriesGlobal: (2048 + 4096) * 2 / 3,
SockRevNatEntries: 4096,
PolicyMapEntries: 16384,
LBMapEntries: 65536,
FragmentsMapEntries: defaults.FragmentsMapEntries,
WantErr: false,
},
},
{
name: "NAT map auto sizing outside of range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
CTMapEntriesGlobalTCP: 2048,
CTMapEntriesGlobalAny: 4096,
NATMapEntriesGlobal: 8192,
},
want: sizes{
CTMapEntriesGlobalTCP: 2048,
CTMapEntriesGlobalAny: 4096,
NATMapEntriesGlobal: 8192,
WantErr: true,
},
},
{
name: "Policy map size below range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
PolicyMapEntries: PolicyMapMin - 1,
},
want: sizes{
PolicyMapEntries: PolicyMapMin - 1,
WantErr: true,
},
},
{
name: "Policy map size above range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
PolicyMapEntries: PolicyMapMax + 1,
},
want: sizes{
PolicyMapEntries: PolicyMapMax + 1,
WantErr: true,
},
},
{
name: "Fragments map size below range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
FragmentsMapEntries: FragmentsMapMin - 1,
},
want: sizes{
FragmentsMapEntries: FragmentsMapMin - 1,
WantErr: true,
},
},
{
name: "Fragments map size above range",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
FragmentsMapEntries: FragmentsMapMax + 1,
},
want: sizes{
FragmentsMapEntries: FragmentsMapMax + 1,
WantErr: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.d.checkMapSizeLimits()
got := sizes{
AuthMapEntries: tt.d.AuthMapEntries,
CTMapEntriesGlobalTCP: tt.d.CTMapEntriesGlobalTCP,
CTMapEntriesGlobalAny: tt.d.CTMapEntriesGlobalAny,
NATMapEntriesGlobal: tt.d.NATMapEntriesGlobal,
PolicyMapEntries: tt.d.PolicyMapEntries,
LBMapEntries: tt.d.LBMapEntries,
FragmentsMapEntries: tt.d.FragmentsMapEntries,
NeighMapEntriesGlobal: tt.d.NeighMapEntriesGlobal,
SockRevNatEntries: tt.d.SockRevNatEntries,
WantErr: err != nil,
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("DaemonConfig.checkMapSizeLimits mismatch for '%s' (-want +got):\n%s", tt.name, diff)
if err != nil {
t.Error(err)
}
}
})
}
}
func TestCheckIPv4NativeRoutingCIDR(t *testing.T) {
tests := []struct {
name string
d *DaemonConfig
wantErr bool
}{
{
name: "with native routing cidr",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
IPAM: ipamOption.IPAMAzure,
IPv4NativeRoutingCIDR: cidr.MustParseCIDR("10.127.64.0/18"),
EnableIPv4: true,
},
wantErr: false,
},
{
name: "without native routing cidr and no masquerade",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: false,
EnableIPv6Masquerade: false,
RoutingMode: RoutingModeNative,
IPAM: ipamOption.IPAMAzure,
EnableIPv4: true,
},
wantErr: false,
},
{
name: "without native routing cidr and tunnel enabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeTunnel,
IPAM: ipamOption.IPAMAzure,
EnableIPv4: true,
},
wantErr: false,
},
{
name: "without native routing cidr and tunnel disabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
IPAM: ipamOption.IPAMENI,
EnableIPv4: true,
},
wantErr: false,
},
{
name: "without native routing cidr and with masquerade and tunnel disabled and ipam not eni",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
IPAM: ipamOption.IPAMAzure,
EnableIPv4: true,
},
wantErr: true,
},
{
name: "without native routing cidr and tunnel disabled, but ipmasq-agent",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
IPAM: ipamOption.IPAMKubernetes,
EnableIPv4: true,
EnableIPMasqAgent: true,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.d.checkIPv4NativeRoutingCIDR()
if tt.wantErr && err == nil {
t.Error("expected error, but got nil")
} else if !tt.wantErr && err != nil {
t.Errorf("expected no error, but got %q", err)
}
})
}
}
func TestCheckIPv6NativeRoutingCIDR(t *testing.T) {
tests := []struct {
name string
d *DaemonConfig
wantErr bool
}{
{
name: "with native routing cidr",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
IPv6NativeRoutingCIDR: cidr.MustParseCIDR("fd00::/120"),
EnableIPv6: true,
},
wantErr: false,
},
{
name: "without native routing cidr and no masquerade",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: false,
EnableIPv6Masquerade: false,
RoutingMode: RoutingModeNative,
EnableIPv6: true,
},
wantErr: false,
},
{
name: "without native routing cidr and tunnel enabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeTunnel,
EnableIPv6: true,
},
wantErr: false,
},
{
name: "without native routing cidr and tunnel disabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
EnableIPv6: true,
},
wantErr: true,
},
{
name: "without native routing cidr and tunnel disabled, but ipmasq-agent",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
EnableIPv4Masquerade: true,
EnableIPv6Masquerade: true,
RoutingMode: RoutingModeNative,
EnableIPv6: true,
EnableIPMasqAgent: true,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.d.checkIPv6NativeRoutingCIDR()
if tt.wantErr && err == nil {
t.Error("expected error, but got nil")
} else if !tt.wantErr && err != nil {
t.Errorf("expected no error, but got %q", err)
}
})
}
}
func TestCheckIPAMDelegatedPlugin(t *testing.T) {
tests := []struct {
name string
d *DaemonConfig
expectErr error
}{
{
name: "IPAMDelegatedPlugin with local router IPv4 set and endpoint health checking disabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMDelegatedPlugin,
EnableIPv4: true,
LocalRouterIPv4: "169.254.0.0",
},
expectErr: nil,
},
{
name: "IPAMDelegatedPlugin with local router IPv6 set and endpoint health checking disabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMDelegatedPlugin,
EnableIPv6: true,
LocalRouterIPv6: "fe80::1",
},
expectErr: nil,
},
{
name: "IPAMDelegatedPlugin with health checking enabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMDelegatedPlugin,
EnableHealthChecking: true,
EnableEndpointHealthChecking: true,
},
expectErr: fmt.Errorf("--enable-endpoint-health-checking must be disabled with --ipam=delegated-plugin"),
},
{
name: "IPAMDelegatedPlugin without local router IPv4",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMDelegatedPlugin,
EnableIPv4: true,
},
expectErr: fmt.Errorf("--local-router-ipv4 must be provided when IPv4 is enabled with --ipam=delegated-plugin"),
},
{
name: "IPAMDelegatedPlugin without local router IPv6",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMDelegatedPlugin,
EnableIPv6: true,
},
expectErr: fmt.Errorf("--local-router-ipv6 must be provided when IPv6 is enabled with --ipam=delegated-plugin"),
},
{
name: "IPAMDelegatedPlugin with envoy config enabled",
d: &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
IPAM: ipamOption.IPAMDelegatedPlugin,
EnableEnvoyConfig: true,
},
expectErr: fmt.Errorf("--enable-envoy-config must be disabled with --ipam=delegated-plugin"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.d.checkIPAMDelegatedPlugin()
if tt.expectErr != nil && err == nil {
t.Errorf("expected error but got none")
} else if tt.expectErr == nil && err != nil {
t.Errorf("expected no error but got %q", err)
} else if tt.expectErr != nil && tt.expectErr.Error() != err.Error() {
t.Errorf("expected error %q but got %q", tt.expectErr, err)
}
})
}
}
func Test_populateNodePortRange(t *testing.T) {
vp := viper.New()
reset := func() { vp = viper.New() }
type want struct {
wantMin int
wantMax int
wantErr bool
}
tests := []struct {
name string
want want
preTestRun func()
}{
{
name: "NodePortRange is valid",
want: want{
wantMin: 23,
wantMax: 24,
wantErr: false,
},
preTestRun: func() {
vp.Set(NodePortRange, []string{"23", "24"})
},
},
{
name: "NodePortRange not set in viper",
want: want{
wantMin: NodePortMinDefault,
wantMax: NodePortMaxDefault,
wantErr: false,
},
preTestRun: func() {
reset()
fs := flag.NewFlagSet(NodePortRange, flag.ContinueOnError)
fs.StringSlice(
NodePortRange,
[]string{
fmt.Sprintf("%d", NodePortMinDefault),
fmt.Sprintf("%d", NodePortMaxDefault),
},
"")
BindEnv(vp, NodePortRange)
vp.BindPFlags(fs)
},
},
{
name: "NodePortMin greater than NodePortMax",
want: want{
wantMin: 666,
wantMax: 555,
wantErr: true,
},
preTestRun: func() {
reset()
vp.Set(NodePortRange, []string{"666", "555"})
},
},
{
name: "NodePortMin equal NodePortMax",
want: want{
wantMin: 666,
wantMax: 666,
wantErr: true,
},
preTestRun: func() {
reset()
vp.Set(NodePortRange, []string{"666", "666"})
},
},
{
name: "NodePortMin not a number",
want: want{
wantMin: 0,
wantMax: 0,
wantErr: true,
},
preTestRun: func() {
reset()
vp.Set(NodePortRange, []string{"aaa", "0"})
},
},
{
name: "NodePortMax not a number",
want: want{
wantMin: 1024,
wantMax: 0,
wantErr: true,
},
preTestRun: func() {
reset()
vp.Set(NodePortRange, []string{"1024", "aaa"})
},
},
{
name: "NodePortRange slice length not equal 2",
want: want{
wantMin: 0,
wantMax: 0,
wantErr: true,
},
preTestRun: func() {
reset()
fs := flag.NewFlagSet(NodePortRange, flag.ContinueOnError)
fs.StringSlice(
NodePortRange,
[]string{
fmt.Sprintf("%d", NodePortMinDefault),
fmt.Sprintf("%d", NodePortMaxDefault),
},
"")
BindEnv(vp, NodePortRange)
vp.BindPFlags(fs)
vp.Set(NodePortRange, []string{"1024"})
},
},
{
// We simply just want to warn the user in this case.
name: "NodePortRange passed as empty",
want: want{
wantMin: 0,
wantMax: 0,
wantErr: false,
},
preTestRun: func() {
reset()
fs := flag.NewFlagSet(NodePortRange, flag.ContinueOnError)
fs.StringSlice(
NodePortRange,
[]string{}, // Explicitly has no defaults.
"")
BindEnv(vp, NodePortRange)
vp.BindPFlags(fs)
vp.Set(NodePortRange, []string{})
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.preTestRun()
d := &DaemonConfig{}
err := d.populateNodePortRange(vp)
got := want{
wantMin: d.NodePortMin,
wantMax: d.NodePortMax,
wantErr: err != nil,
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("DaemonConfig.populateNodePortRange = got %v, want %v", got, tt.want)
}
})
}
}
func TestGetDefaultMonitorQueueSize(t *testing.T) {
require.Equal(t, 4*defaults.MonitorQueueSizePerCPU, getDefaultMonitorQueueSize(4))
require.Equal(t, defaults.MonitorQueueSizePerCPUMaximum, getDefaultMonitorQueueSize(1000))
}
const (
_ = iota
KiB = 1 << (10 * iota)
MiB
GiB
)
func TestBPFMapSizeCalculation(t *testing.T) {
type sizes struct {
CTMapSizeTCP int
CTMapSizeAny int
NATMapSize int
NeighMapSize int
SockRevNatMapSize int
}
tests := []struct {
name string
totalMemory uint64
ratio float64
want sizes
preTestRun func(*viper.Viper)
}{
{
name: "static default sizes",
// zero memory and ratio: skip calculateDynamicBPFMapSizes
want: sizes{
CTMapSizeTCP: CTMapEntriesGlobalTCPDefault,
CTMapSizeAny: CTMapEntriesGlobalAnyDefault,
NATMapSize: NATMapEntriesGlobalDefault,
NeighMapSize: NATMapEntriesGlobalDefault,
SockRevNatMapSize: SockRevNATMapEntriesDefault,
},
preTestRun: func(vp *viper.Viper) {
vp.Set(CTMapEntriesGlobalTCPName, CTMapEntriesGlobalTCPDefault)
vp.Set(CTMapEntriesGlobalAnyName, CTMapEntriesGlobalAnyDefault)
vp.Set(NATMapEntriesGlobalName, NATMapEntriesGlobalDefault)
// Neigh table has the same number of entries as NAT Map has.
vp.Set(NeighMapEntriesGlobalName, NATMapEntriesGlobalDefault)
vp.Set(SockRevNatEntriesName, SockRevNATMapEntriesDefault)
},
},
{
name: "static, non-default sizes inside range",
// zero memory and ratio: skip calculateDynamicBPFMapSizes
want: sizes{
CTMapSizeTCP: CTMapEntriesGlobalTCPDefault + 128,
CTMapSizeAny: CTMapEntriesGlobalAnyDefault - 64,
NATMapSize: NATMapEntriesGlobalDefault + 256,
NeighMapSize: NATMapEntriesGlobalDefault + 256,
SockRevNatMapSize: SockRevNATMapEntriesDefault + 256,
},
preTestRun: func(vp *viper.Viper) {
vp.Set(CTMapEntriesGlobalTCPName, CTMapEntriesGlobalTCPDefault+128)
vp.Set(CTMapEntriesGlobalAnyName, CTMapEntriesGlobalAnyDefault-64)
vp.Set(NATMapEntriesGlobalName, NATMapEntriesGlobalDefault+256)
// Neigh table has the same number of entries as NAT Map has.
vp.Set(NeighMapEntriesGlobalName, NATMapEntriesGlobalDefault+256)
vp.Set(SockRevNatEntriesName, SockRevNATMapEntriesDefault+256)
},
},
{
name: "dynamic size without any static sizes (512MB, 0.25%)",
totalMemory: 512 * MiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: LimitTableAutoGlobalTCPMin,
CTMapSizeAny: LimitTableAutoGlobalAnyMin,
NATMapSize: LimitTableAutoNatGlobalMin,
NeighMapSize: LimitTableAutoNatGlobalMin,
SockRevNatMapSize: LimitTableAutoSockRevNatMin,
},
},
{
name: "dynamic size without any static sizes (1GiB, 0.25%)",
totalMemory: 1 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: LimitTableAutoGlobalTCPMin,
CTMapSizeAny: LimitTableAutoGlobalAnyMin,
NATMapSize: LimitTableAutoNatGlobalMin,
NeighMapSize: LimitTableAutoNatGlobalMin,
SockRevNatMapSize: LimitTableAutoSockRevNatMin,
},
},
{
name: "dynamic size without any static sizes (2GiB, 0.25%)",
totalMemory: 2 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: LimitTableAutoGlobalTCPMin,
CTMapSizeAny: LimitTableAutoGlobalAnyMin,
NATMapSize: LimitTableAutoNatGlobalMin,
NeighMapSize: LimitTableAutoNatGlobalMin,
SockRevNatMapSize: LimitTableAutoSockRevNatMin,
},
},
{
name: "dynamic size without any static sizes (7.5GiB, 0.25%)",
totalMemory: 7.5 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: LimitTableAutoGlobalTCPMin,
CTMapSizeAny: LimitTableAutoGlobalAnyMin,
NATMapSize: LimitTableAutoNatGlobalMin,
NeighMapSize: LimitTableAutoNatGlobalMin,
SockRevNatMapSize: LimitTableAutoSockRevNatMin,
},
},
{
name: "dynamic size without any static sizes (16GiB, 0.25%)",
totalMemory: 16 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: 151765,
CTMapSizeAny: 75882,
NATMapSize: 151765,
NeighMapSize: 151765,
SockRevNatMapSize: 75882,
},
},
{
name: "dynamic size without any static sizes (120GiB, 0.25%)",
totalMemory: 30 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: 284560,
CTMapSizeAny: 142280,
NATMapSize: 284560,
NeighMapSize: 284560,
SockRevNatMapSize: 142280,
},
},
{
name: "dynamic size without any static sizes (240GiB, 0.25%)",
totalMemory: 240 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: 2276484,
CTMapSizeAny: 1138242,
NATMapSize: 2276484,
NeighMapSize: 2276484,
SockRevNatMapSize: 1138242,
},
},
{
name: "dynamic size without any static sizes (360GiB, 0.25%)",
totalMemory: 360 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: 3414726,
CTMapSizeAny: 1707363,
NATMapSize: 3414726,
NeighMapSize: 3414726,
SockRevNatMapSize: 1707363,
},
},
{
name: "dynamic size with static CT TCP size (4GiB, 0.25%)",
totalMemory: 4 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: CTMapEntriesGlobalTCPDefault + 1024,
CTMapSizeAny: 65536,
NATMapSize: 131072,
NeighMapSize: 131072,
SockRevNatMapSize: 65536,
},
preTestRun: func(vp *viper.Viper) {
vp.Set(CTMapEntriesGlobalTCPName, CTMapEntriesGlobalTCPDefault+1024)
},
},
{
name: "huge dynamic size ratio gets clamped (8GiB, 98%)",
totalMemory: 16 * GiB,
ratio: 0.98,
want: sizes{
CTMapSizeTCP: LimitTableMax,
CTMapSizeAny: LimitTableMax,
NATMapSize: LimitTableMax,
NeighMapSize: LimitTableMax,
SockRevNatMapSize: LimitTableMax,
},
},
{
name: "dynamic size NAT size above limit with static CT sizes (issue #13843)",
totalMemory: 128 * GiB,
ratio: 0.0025,
want: sizes{
CTMapSizeTCP: 524288,
CTMapSizeAny: 262144,
NATMapSize: (524288 + 262144) * 2 / 3,
NeighMapSize: 524288,
SockRevNatMapSize: 607062,
},
preTestRun: func(vp *viper.Viper) {
vp.Set(CTMapEntriesGlobalTCPName, 524288)
vp.Set(CTMapEntriesGlobalAnyName, 262144)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
vp := viper.New()
if tt.preTestRun != nil {
tt.preTestRun(vp)
}
d := &DaemonConfig{
ConfigPatchMutex: new(lock.RWMutex),
CTMapEntriesGlobalTCP: vp.GetInt(CTMapEntriesGlobalTCPName),
CTMapEntriesGlobalAny: vp.GetInt(CTMapEntriesGlobalAnyName),
NATMapEntriesGlobal: vp.GetInt(NATMapEntriesGlobalName),
NeighMapEntriesGlobal: vp.GetInt(NeighMapEntriesGlobalName),
SockRevNatEntries: vp.GetInt(SockRevNatEntriesName),
}
// cannot set these from the Sizeof* consts from
// pkg/maps/* due to circular dependencies.
d.SetMapElementSizes(
94, // ctmap.SizeofCTKey + policymap.SizeofCTEntry
94, // nat.SizeofNATKey + nat.SizeofNATEntry
24, // neighborsmap.SizeofNeighKey6 + neighborsmap.SizeOfNeighValue
48, // lbmap.SizeofSockRevNat6Key+lbmap.SizeofSockRevNat6Value
)
if tt.totalMemory > 0 && tt.ratio > 0.0 {
d.calculateDynamicBPFMapSizes(vp, tt.totalMemory, tt.ratio)
}
got := sizes{
d.CTMapEntriesGlobalTCP,
d.CTMapEntriesGlobalAny,
d.NATMapEntriesGlobal,
d.NeighMapEntriesGlobal,
d.SockRevNatEntries,
}
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("DaemonConfig.calculateDynamicBPFMapSize (-want +got):\n%s", diff)
}
})
}
}
func Test_backupFiles(t *testing.T) {
tempDir := t.TempDir()
fileNames := []string{"test.json", "test-1.json", "test-2.json"}
backupFiles(tempDir, fileNames)
files, err := os.ReadDir(tempDir)
require.NoError(t, err)
// No files should have been created
require.Len(t, files, 0)
_, err = os.Create(filepath.Join(tempDir, "test.json"))
require.NoError(t, err)
backupFiles(tempDir, fileNames)
files, err = os.ReadDir(tempDir)
require.NoError(t, err)
require.Len(t, files, 1)
require.Equal(t, "test-1.json", files[0].Name())
backupFiles(tempDir, fileNames)
files, err = os.ReadDir(tempDir)
require.NoError(t, err)
require.Len(t, files, 1)
require.Equal(t, "test-2.json", files[0].Name())
_, err = os.Create(filepath.Join(tempDir, "test.json"))
require.NoError(t, err)
backupFiles(tempDir, fileNames)
files, err = os.ReadDir(tempDir)
require.NoError(t, err)
require.Len(t, files, 2)
require.Equal(t, "test-1.json", files[0].Name())
require.Equal(t, "test-2.json", files[1].Name())
}
func Test_parseEventBufferTupleString(t *testing.T) {
assert := assert.New(t)
c, err := ParseEventBufferTupleString("enabled,123,1h")
assert.NoError(err)
assert.True(c.Enabled)
assert.Equal(123, c.MaxSize)
assert.Equal(time.Hour, c.TTL)
c, err = ParseEventBufferTupleString("disabled,123,1h")
assert.NoError(err)
assert.False(c.Enabled)
assert.Equal(123, c.MaxSize)
assert.Equal(time.Hour, c.TTL)
c, err = ParseEventBufferTupleString("cat,123,1h")
assert.Error(err)
c, err = ParseEventBufferTupleString("enabled,xxx,1h")
assert.Error(err)
c, err = ParseEventBufferTupleString("enabled,123,x")
assert.Error(err)
}
func TestDaemonConfig_validateContainerIPLocalReservedPorts(t *testing.T) {
tests := []struct {
name string
value string
wantErr assert.ErrorAssertionFunc
}{
{
name: "default",
value: "auto",
wantErr: assert.NoError,
},
{
name: "empty",
value: "",
wantErr: assert.NoError,
},
{
name: "single port",
value: "1000",
wantErr: assert.NoError,
},
{
name: "single range",
value: "1000-2000",
wantErr: assert.NoError,
},
{
name: "port list",
value: "1000,2000",
wantErr: assert.NoError,
},
{
name: "port range list",
value: "1000-1001,2000-2002",
wantErr: assert.NoError,
},
{
name: "mixed",
value: "1000,2000-2002,3000,4000-4004",
wantErr: assert.NoError,
},
{
name: "trailing comma",
value: "1,2,3,",
wantErr: assert.Error,
},
{
name: "leading comma",
value: ",1,2,3",
wantErr: assert.Error,
},
{
name: "invalid range",
value: "-",
wantErr: assert.Error,
},
{
name: "invalid range end",
value: "1000-",
wantErr: assert.Error,
},
{
name: "invalid range start",
value: "-1000",
wantErr: assert.Error,
},
{
name: "invalid port",
value: "foo",
wantErr: assert.Error,
},
{
name: "too many commas",
value: "1000,,2000",
wantErr: assert.Error,
},
{
name: "invalid second value",
value: "1000,-",
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &DaemonConfig{ContainerIPLocalReservedPorts: tt.value}
tt.wantErr(t, c.validateContainerIPLocalReservedPorts(), "validateContainerIPLocalReservedPorts()")
})
}
}
func TestDaemonConfig_StoreInFile(t *testing.T) {
err := Config.StoreInFile(".")
assert.NoError(t, err)
err = Config.ValidateUnchanged(context.Background())
assert.NoError(t, err)
// minor change
Config.DryMode = true
err = Config.ValidateUnchanged(context.Background())
assert.Error(t, err)
assert.ErrorContains(t, err, "Config differs:", "Should return a validation error")
Config.DryMode = false
// IntOptions changes are ignored
assert.False(t, Config.Opts.IsEnabled("unit-test-key-only")) // make sure not used
Config.Opts.SetBool("unit-test-key-only", true)
err = Config.ValidateUnchanged(context.Background())
assert.NoError(t, err)
Config.Opts.Delete("unit-test-key-only")
}
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...