blob: 7eee58376b9d3191006e42439fa8f69a948f29f2 [file] [log] [blame]
/*
Copyright 2015 The Kubernetes 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 app
import (
"fmt"
"reflect"
"runtime"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
apimachineryconfig "k8s.io/apimachinery/pkg/apis/config"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
api "k8s.io/kubernetes/pkg/apis/core"
kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
"k8s.io/kubernetes/pkg/util/configz"
utilpointer "k8s.io/utils/pointer"
)
type fakeNodeInterface struct {
node api.Node
}
func (fake *fakeNodeInterface) Get(hostname string, options metav1.GetOptions) (*api.Node, error) {
return &fake.node, nil
}
type fakeIPTablesVersioner struct {
version string // what to return
err error // what to return
}
func (fake *fakeIPTablesVersioner) GetVersion() (string, error) {
return fake.version, fake.err
}
func (fake *fakeIPTablesVersioner) IsCompatible() error {
return fake.err
}
type fakeIPSetVersioner struct {
version string // what to return
err error // what to return
}
func (fake *fakeIPSetVersioner) GetVersion() (string, error) {
return fake.version, fake.err
}
type fakeKernelCompatTester struct {
ok bool
}
func (fake *fakeKernelCompatTester) IsCompatible() error {
if !fake.ok {
return fmt.Errorf("error")
}
return nil
}
// fakeKernelHandler implements KernelHandler.
type fakeKernelHandler struct {
modules []string
}
func (fake *fakeKernelHandler) GetModules() ([]string, error) {
return fake.modules, nil
}
// This test verifies that NewProxyServer does not crash when CleanupAndExit is true.
func TestProxyServerWithCleanupAndExit(t *testing.T) {
// Each bind address below is a separate test case
bindAddresses := []string{
"0.0.0.0",
"::",
}
for _, addr := range bindAddresses {
options := NewOptions()
options.config = &kubeproxyconfig.KubeProxyConfiguration{
BindAddress: addr,
}
options.CleanupAndExit = true
proxyserver, err := NewProxyServer(options)
assert.Nil(t, err, "unexpected error in NewProxyServer, addr: %s", addr)
assert.NotNil(t, proxyserver, "nil proxy server obj, addr: %s", addr)
assert.NotNil(t, proxyserver.IptInterface, "nil iptables intf, addr: %s", addr)
assert.True(t, proxyserver.CleanupAndExit, "false CleanupAndExit, addr: %s", addr)
// Clean up config for next test case
configz.Delete(kubeproxyconfig.GroupName)
}
}
func TestGetConntrackMax(t *testing.T) {
ncores := runtime.NumCPU()
testCases := []struct {
min int32
max int32
maxPerCore int32
expected int
err string
}{
{
expected: 0,
},
{
max: 12345,
expected: 12345,
},
{
max: 12345,
maxPerCore: 67890,
expected: -1,
err: "mutually exclusive",
},
{
maxPerCore: 67890, // use this if Max is 0
min: 1, // avoid 0 default
expected: 67890 * ncores,
},
{
maxPerCore: 1, // ensure that Min is considered
min: 123456,
expected: 123456,
},
{
maxPerCore: 0, // leave system setting
min: 123456,
expected: 0,
},
}
for i, tc := range testCases {
cfg := kubeproxyconfig.KubeProxyConntrackConfiguration{
Min: utilpointer.Int32Ptr(tc.min),
Max: utilpointer.Int32Ptr(tc.max),
MaxPerCore: utilpointer.Int32Ptr(tc.maxPerCore),
}
x, e := getConntrackMax(cfg)
if e != nil {
if tc.err == "" {
t.Errorf("[%d] unexpected error: %v", i, e)
} else if !strings.Contains(e.Error(), tc.err) {
t.Errorf("[%d] expected an error containing %q: %v", i, tc.err, e)
}
} else if x != tc.expected {
t.Errorf("[%d] expected %d, got %d", i, tc.expected, x)
}
}
}
// TestLoadConfig tests proper operation of loadConfig()
func TestLoadConfig(t *testing.T) {
yamlTemplate := `apiVersion: kubeproxy.config.k8s.io/v1alpha1
bindAddress: %s
clientConnection:
acceptContentTypes: "abc"
burst: 100
contentType: content-type
kubeconfig: "/path/to/kubeconfig"
qps: 7
clusterCIDR: "%s"
configSyncPeriod: 15s
conntrack:
max: 4
maxPerCore: 2
min: 1
tcpCloseWaitTimeout: 10s
tcpEstablishedTimeout: 20s
healthzBindAddress: "%s"
hostnameOverride: "foo"
iptables:
masqueradeAll: true
masqueradeBit: 17
minSyncPeriod: 10s
syncPeriod: 60s
ipvs:
minSyncPeriod: 10s
syncPeriod: 60s
excludeCIDRs:
- "10.20.30.40/16"
- "fd00:1::0/64"
kind: KubeProxyConfiguration
metricsBindAddress: "%s"
mode: "%s"
oomScoreAdj: 17
portRange: "2-7"
resourceContainer: /foo
udpIdleTimeout: 123ms
nodePortAddresses:
- "10.20.30.40/16"
- "fd00:1::0/64"
`
testCases := []struct {
name string
mode string
bindAddress string
clusterCIDR string
healthzBindAddress string
metricsBindAddress string
}{
{
name: "iptables mode, IPv4 all-zeros bind address",
mode: "iptables",
bindAddress: "0.0.0.0",
clusterCIDR: "1.2.3.0/24",
healthzBindAddress: "1.2.3.4:12345",
metricsBindAddress: "2.3.4.5:23456",
},
{
name: "iptables mode, non-zeros IPv4 config",
mode: "iptables",
bindAddress: "9.8.7.6",
clusterCIDR: "1.2.3.0/24",
healthzBindAddress: "1.2.3.4:12345",
metricsBindAddress: "2.3.4.5:23456",
},
{
// Test for 'bindAddress: "::"' (IPv6 all-zeros) in kube-proxy
// config file. The user will need to put quotes around '::' since
// 'bindAddress: ::' is invalid yaml syntax.
name: "iptables mode, IPv6 \"::\" bind address",
mode: "iptables",
bindAddress: "\"::\"",
clusterCIDR: "fd00:1::0/64",
healthzBindAddress: "[fd00:1::5]:12345",
metricsBindAddress: "[fd00:2::5]:23456",
},
{
// Test for 'bindAddress: "[::]"' (IPv6 all-zeros in brackets)
// in kube-proxy config file. The user will need to use
// surrounding quotes here since 'bindAddress: [::]' is invalid
// yaml syntax.
name: "iptables mode, IPv6 \"[::]\" bind address",
mode: "iptables",
bindAddress: "\"[::]\"",
clusterCIDR: "fd00:1::0/64",
healthzBindAddress: "[fd00:1::5]:12345",
metricsBindAddress: "[fd00:2::5]:23456",
},
{
// Test for 'bindAddress: ::0' (another form of IPv6 all-zeros).
// No surrounding quotes are required around '::0'.
name: "iptables mode, IPv6 ::0 bind address",
mode: "iptables",
bindAddress: "::0",
clusterCIDR: "fd00:1::0/64",
healthzBindAddress: "[fd00:1::5]:12345",
metricsBindAddress: "[fd00:2::5]:23456",
},
{
name: "ipvs mode, IPv6 config",
mode: "ipvs",
bindAddress: "2001:db8::1",
clusterCIDR: "fd00:1::0/64",
healthzBindAddress: "[fd00:1::5]:12345",
metricsBindAddress: "[fd00:2::5]:23456",
},
}
for _, tc := range testCases {
expBindAddr := tc.bindAddress
if tc.bindAddress[0] == '"' {
// Surrounding double quotes will get stripped by the yaml parser.
expBindAddr = expBindAddr[1 : len(tc.bindAddress)-1]
}
expected := &kubeproxyconfig.KubeProxyConfiguration{
BindAddress: expBindAddr,
ClientConnection: apimachineryconfig.ClientConnectionConfiguration{
AcceptContentTypes: "abc",
Burst: 100,
ContentType: "content-type",
Kubeconfig: "/path/to/kubeconfig",
QPS: 7,
},
ClusterCIDR: tc.clusterCIDR,
ConfigSyncPeriod: metav1.Duration{Duration: 15 * time.Second},
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
Max: utilpointer.Int32Ptr(4),
MaxPerCore: utilpointer.Int32Ptr(2),
Min: utilpointer.Int32Ptr(1),
TCPCloseWaitTimeout: &metav1.Duration{Duration: 10 * time.Second},
TCPEstablishedTimeout: &metav1.Duration{Duration: 20 * time.Second},
},
FeatureGates: map[string]bool{},
HealthzBindAddress: tc.healthzBindAddress,
HostnameOverride: "foo",
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true,
MasqueradeBit: utilpointer.Int32Ptr(17),
MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
SyncPeriod: metav1.Duration{Duration: 60 * time.Second},
},
IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
SyncPeriod: metav1.Duration{Duration: 60 * time.Second},
ExcludeCIDRs: []string{"10.20.30.40/16", "fd00:1::0/64"},
},
MetricsBindAddress: tc.metricsBindAddress,
Mode: kubeproxyconfig.ProxyMode(tc.mode),
OOMScoreAdj: utilpointer.Int32Ptr(17),
PortRange: "2-7",
ResourceContainer: "/foo",
UDPIdleTimeout: metav1.Duration{Duration: 123 * time.Millisecond},
NodePortAddresses: []string{"10.20.30.40/16", "fd00:1::0/64"},
}
options := NewOptions()
yaml := fmt.Sprintf(
yamlTemplate, tc.bindAddress, tc.clusterCIDR,
tc.healthzBindAddress, tc.metricsBindAddress, tc.mode)
config, err := options.loadConfig([]byte(yaml))
assert.NoError(t, err, "unexpected error for %s: %v", tc.name, err)
if !reflect.DeepEqual(expected, config) {
t.Fatalf("unexpected config for %s, diff = %s", tc.name, diff.ObjectDiff(config, expected))
}
}
}
// TestLoadConfigFailures tests failure modes for loadConfig()
func TestLoadConfigFailures(t *testing.T) {
testCases := []struct {
name string
config string
expErr string
}{
{
name: "Decode error test",
config: "Twas bryllyg, and ye slythy toves",
expErr: "could not find expected ':'",
},
{
name: "Bad config type test",
config: "kind: KubeSchedulerConfiguration",
expErr: "no kind",
},
{
name: "Missing quotes around :: bindAddress",
config: "bindAddress: ::",
expErr: "mapping values are not allowed in this context",
},
}
version := "apiVersion: kubeproxy.config.k8s.io/v1alpha1"
for _, tc := range testCases {
options := NewOptions()
config := fmt.Sprintf("%s\n%s", version, tc.config)
_, err := options.loadConfig([]byte(config))
if assert.Error(t, err, tc.name) {
assert.Contains(t, err.Error(), tc.expErr, tc.name)
}
}
}
// TestProcessHostnameOverrideFlag tests processing hostname-override arg
func TestProcessHostnameOverrideFlag(t *testing.T) {
testCases := []struct {
name string
hostnameOverrideFlag string
expectedHostname string
}{
{
name: "Hostname from config file",
hostnameOverrideFlag: "",
expectedHostname: "foo",
},
{
name: "Hostname from flag",
hostnameOverrideFlag: " bar ",
expectedHostname: "bar",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
options := NewOptions()
options.config = &kubeproxyconfig.KubeProxyConfiguration{
HostnameOverride: "foo",
}
options.hostnameOverride = tc.hostnameOverrideFlag
err := options.processHostnameOverrideFlag()
assert.NoError(t, err, "unexpected error %v", err)
if tc.expectedHostname != options.config.HostnameOverride {
t.Fatalf("expected hostname: %s, but got: %s", tc.expectedHostname, options.config.HostnameOverride)
}
})
}
}