diff --git a/cmd/kube-proxy/app/BUILD b/cmd/kube-proxy/app/BUILD index 6a65b424b61..93683c5ecd4 100644 --- a/cmd/kube-proxy/app/BUILD +++ b/cmd/kube-proxy/app/BUILD @@ -73,10 +73,13 @@ go_test( deps = [ "//pkg/api:go_default_library", "//pkg/apis/componentconfig:go_default_library", + "//pkg/apis/componentconfig/v1alpha1:go_default_library", "//pkg/util:go_default_library", + "//pkg/util/configz:go_default_library", "//pkg/util/iptables:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", ], ) diff --git a/cmd/kube-proxy/app/server_test.go b/cmd/kube-proxy/app/server_test.go index b7337fe6c2b..673ae0ae79b 100644 --- a/cmd/kube-proxy/app/server_test.go +++ b/cmd/kube-proxy/app/server_test.go @@ -17,6 +17,7 @@ limitations under the License. package app import ( + "errors" "fmt" "reflect" "runtime" @@ -27,10 +28,13 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sRuntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apis/componentconfig" + "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1" "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/configz" "k8s.io/kubernetes/pkg/util/iptables" ) @@ -134,23 +138,67 @@ func Test_getProxyMode(t *testing.T) { } } -// This test verifies that Proxy Server does not crash when CleanupAndExit is true. +// TestNewOptionsFailures tests failure modes for NewOptions() +func TestNewOptionsFailures(t *testing.T) { + + // Create a fake scheme builder that generates an error + errString := fmt.Sprintf("Simulated error") + genError := func(scheme *k8sRuntime.Scheme) error { + return errors.New(errString) + } + fakeSchemeBuilder := k8sRuntime.NewSchemeBuilder(genError) + + simulatedErrorTest := func(target string) { + var addToScheme *func(s *k8sRuntime.Scheme) error + if target == "componentconfig" { + addToScheme = &componentconfig.AddToScheme + } else { + addToScheme = &v1alpha1.AddToScheme + } + restoreValue := *addToScheme + restore := func() { + *addToScheme = restoreValue + } + defer restore() + *addToScheme = fakeSchemeBuilder.AddToScheme + _, err := NewOptions() + assert.Error(t, err, fmt.Sprintf("Simulated error in component %s", target)) + } + + // Simulate errors in calls to AddToScheme() + faultTargets := []string{"componentconfig", "v1alpha1"} + for _, target := range faultTargets { + simulatedErrorTest(target) + } +} + +// This test verifies that NewProxyServer does not crash when CleanupAndExit is true. func TestProxyServerWithCleanupAndExit(t *testing.T) { - options, err := NewOptions() - if err != nil { - t.Fatal(err) + // Each bind address below is a separate test case + bindAddresses := []string{ + "0.0.0.0", + "2001:db8::1", } + for _, addr := range bindAddresses { + options, err := NewOptions() + if err != nil { + t.Fatalf("Unexpected error with address %s: %v", addr, err) + } - options.config = &componentconfig.KubeProxyConfiguration{ - BindAddress: "0.0.0.0", + options.config = &componentconfig.KubeProxyConfiguration{ + BindAddress: addr, + } + options.CleanupAndExit = true + + proxyserver, err := NewProxyServer(options.config, options.CleanupAndExit, options.scheme, options.master) + + 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) + + // Clean up config for next test case + configz.Delete("componentconfig") } - options.CleanupAndExit = true - - proxyserver, err := NewProxyServer(options.config, options.CleanupAndExit, options.scheme, options.master) - - assert.Nil(t, err) - assert.NotNil(t, proxyserver) - assert.NotNil(t, proxyserver.IptInterface) } func TestGetConntrackMax(t *testing.T) { @@ -211,16 +259,18 @@ func TestGetConntrackMax(t *testing.T) { } } +// TestLoadConfig tests proper operation of loadConfig() func TestLoadConfig(t *testing.T) { - yaml := `apiVersion: componentconfig/v1alpha1 -bindAddress: 9.8.7.6 + + yamlTemplate := `apiVersion: componentconfig/v1alpha1 +bindAddress: %s clientConnection: acceptContentTypes: "abc" burst: 100 contentType: content-type kubeconfig: "/path/to/kubeconfig" qps: 7 -clusterCIDR: "1.2.3.0/24" +clusterCIDR: "%s" configSyncPeriod: 15s conntrack: max: 4 @@ -229,7 +279,7 @@ conntrack: tcpCloseWaitTimeout: 10s tcpEstablishedTimeout: 20s featureGates: "all" -healthzBindAddress: 1.2.3.4:12345 +healthzBindAddress: "%s" hostnameOverride: "foo" iptables: masqueradeAll: true @@ -237,7 +287,7 @@ iptables: minSyncPeriod: 10s syncPeriod: 60s kind: KubeProxyConfiguration -metricsBindAddress: 2.3.4.5:23456 +metricsBindAddress: "%s" mode: "iptables" oomScoreAdj: 17 portRange: "2-7" @@ -245,47 +295,104 @@ resourceContainer: /foo udpTimeoutMilliseconds: 123ms ` - expected := &componentconfig.KubeProxyConfiguration{ - BindAddress: "9.8.7.6", - ClientConnection: componentconfig.ClientConnectionConfiguration{ - AcceptContentTypes: "abc", - Burst: 100, - ContentType: "content-type", - KubeConfigFile: "/path/to/kubeconfig", - QPS: 7, + testCases := []struct { + name string + bindAddress string + clusterCIDR string + healthzBindAddress string + metricsBindAddress string + }{ + { + name: "IPv4 config", + bindAddress: "9.8.7.6", + clusterCIDR: "1.2.3.0/24", + healthzBindAddress: "1.2.3.4:12345", + metricsBindAddress: "2.3.4.5:23456", }, - ClusterCIDR: "1.2.3.0/24", - ConfigSyncPeriod: metav1.Duration{Duration: 15 * time.Second}, - Conntrack: componentconfig.KubeProxyConntrackConfiguration{ - Max: 4, - MaxPerCore: 2, - Min: 1, - TCPCloseWaitTimeout: metav1.Duration{Duration: 10 * time.Second}, - TCPEstablishedTimeout: metav1.Duration{Duration: 20 * time.Second}, + { + name: "IPv6 config", + bindAddress: "2001:db8::1", + clusterCIDR: "fd00:1::0/64", + healthzBindAddress: "[fd00:1::5]:12345", + metricsBindAddress: "[fd00:2::5]:23456", }, - FeatureGates: "all", - HealthzBindAddress: "1.2.3.4:12345", - HostnameOverride: "foo", - IPTables: componentconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - MasqueradeBit: util.Int32Ptr(17), - MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, - SyncPeriod: metav1.Duration{Duration: 60 * time.Second}, - }, - MetricsBindAddress: "2.3.4.5:23456", - Mode: "iptables", - OOMScoreAdj: util.Int32Ptr(17), - PortRange: "2-7", - ResourceContainer: "/foo", - UDPIdleTimeout: metav1.Duration{Duration: 123 * time.Millisecond}, } - options, err := NewOptions() - assert.NoError(t, err) + for _, tc := range testCases { + expected := &componentconfig.KubeProxyConfiguration{ + BindAddress: tc.bindAddress, + ClientConnection: componentconfig.ClientConnectionConfiguration{ + AcceptContentTypes: "abc", + Burst: 100, + ContentType: "content-type", + KubeConfigFile: "/path/to/kubeconfig", + QPS: 7, + }, + ClusterCIDR: tc.clusterCIDR, + ConfigSyncPeriod: metav1.Duration{Duration: 15 * time.Second}, + Conntrack: componentconfig.KubeProxyConntrackConfiguration{ + Max: 4, + MaxPerCore: 2, + Min: 1, + TCPCloseWaitTimeout: metav1.Duration{Duration: 10 * time.Second}, + TCPEstablishedTimeout: metav1.Duration{Duration: 20 * time.Second}, + }, + FeatureGates: "all", + HealthzBindAddress: tc.healthzBindAddress, + HostnameOverride: "foo", + IPTables: componentconfig.KubeProxyIPTablesConfiguration{ + MasqueradeAll: true, + MasqueradeBit: util.Int32Ptr(17), + MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + SyncPeriod: metav1.Duration{Duration: 60 * time.Second}, + }, + MetricsBindAddress: tc.metricsBindAddress, + Mode: "iptables", + OOMScoreAdj: util.Int32Ptr(17), + PortRange: "2-7", + ResourceContainer: "/foo", + UDPIdleTimeout: metav1.Duration{Duration: 123 * time.Millisecond}, + } - config, err := options.loadConfig([]byte(yaml)) - assert.NoError(t, err) - if !reflect.DeepEqual(expected, config) { - t.Fatalf("unexpected config, diff = %s", diff.ObjectDiff(config, expected)) + options, err := NewOptions() + assert.NoError(t, err, "unexpected error for %s: %v", tc.name, err) + + yaml := fmt.Sprintf( + yamlTemplate, tc.bindAddress, tc.clusterCIDR, + tc.healthzBindAddress, tc.metricsBindAddress) + 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 test, 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: "unexpected config type", + }, + } + version := "apiVersion: componentconfig/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) + } } }