Merge pull request #94902 from cmluciano/cml/proxyvaltesting

proxy: Restructure config validation tests to check errors
This commit is contained in:
Kubernetes Prow Robot 2021-01-13 10:18:36 -08:00 committed by GitHub
commit 5c7ee30eaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -17,9 +17,7 @@ limitations under the License.
package validation package validation
import ( import (
"fmt"
"runtime" "runtime"
"strings"
"testing" "testing"
"time" "time"
@ -188,13 +186,13 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
} }
} }
errorCases := []struct { newPath := field.NewPath("KubeProxyConfiguration")
testCases := map[string]struct {
config kubeproxyconfig.KubeProxyConfiguration config kubeproxyconfig.KubeProxyConfiguration
msg string expectedErrs field.ErrorList
}{ }{
{ "invalid BindAddress": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
// only BindAddress is invalid
BindAddress: "10.10.12.11:2000", BindAddress: "10.10.12.11:2000",
HealthzBindAddress: "0.0.0.0:10256", HealthzBindAddress: "0.0.0.0:10256",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
@ -213,12 +211,11 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "not a valid textual representation of an IP address", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("BindAddress"), "10.10.12.11:2000", "not a valid textual representation of an IP address")},
}, },
{ "invalid HealthzBindAddress": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
// only HealthzBindAddress is invalid
HealthzBindAddress: "0.0.0.0", HealthzBindAddress: "0.0.0.0",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
ClusterCIDR: "192.168.59.0/24", ClusterCIDR: "192.168.59.0/24",
@ -236,13 +233,12 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "must be IP:port", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0.0.0.0", "must be IP:port")},
}, },
{ "invalid MetricsBindAddress": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
// only MetricsBindAddress is invalid
MetricsBindAddress: "127.0.0.1", MetricsBindAddress: "127.0.0.1",
ClusterCIDR: "192.168.59.0/24", ClusterCIDR: "192.168.59.0/24",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
@ -259,14 +255,13 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "must be IP:port", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("MetricsBindAddress"), "127.0.0.1", "must be IP:port")},
}, },
{ "ClusterCIDR missing subset range": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
// only ClusterCIDR is invalid
ClusterCIDR: "192.168.59.0", ClusterCIDR: "192.168.59.0",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
@ -282,9 +277,9 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0", "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
}, },
{ "Two ClusterCIDR addresses provided without DualStack feature-enabled": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
@ -306,9 +301,9 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0/24,fd00:192:168::/64", "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
}, },
{ "DualStack feature-enabled but EndpointSlice feature disabled": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
@ -330,58 +325,9 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "EndpointSlice feature flag must be turned on", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("FeatureGates"), map[string]bool{"EndpointSlice": false, "IPv6DualStack": true}, "EndpointSlice feature flag must be turned on when turning on DualStack")},
}, },
"Invalid number of ClusterCIDRs": {
{
config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
// DualStack with multiple CIDRs but only one IP family
FeatureGates: map[string]bool{"IPv6DualStack": true, "EndpointSlice": true},
ClusterCIDR: "192.168.59.0/24,10.0.0.0/16",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
},
},
msg: "must be a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)",
},
{
config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249",
// DualStack with an invalid subnet
FeatureGates: map[string]bool{"IPv6DualStack": true, "EndpointSlice": true},
ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,a.b.c.d/f",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
},
},
msg: "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)",
},
{
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
@ -402,15 +348,14 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16", "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)")},
}, },
{ "UDPIdleTimeout must be > 0": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
ClusterCIDR: "192.168.59.0/24", ClusterCIDR: "192.168.59.0/24",
// only UDPIdleTimeout is invalid
UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: -1 * time.Second},
ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second},
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
@ -425,16 +370,15 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "must be greater than 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("UDPIdleTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than 0")},
}, },
{ "ConfigSyncPeriod must be > 0": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "10.10.12.11", BindAddress: "10.10.12.11",
HealthzBindAddress: "0.0.0.0:12345", HealthzBindAddress: "0.0.0.0:12345",
MetricsBindAddress: "127.0.0.1:10249", MetricsBindAddress: "127.0.0.1:10249",
ClusterCIDR: "192.168.59.0/24", ClusterCIDR: "192.168.59.0/24",
UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second}, UDPIdleTimeout: metav1.Duration{Duration: 1 * time.Second},
// only ConfigSyncPeriod is invalid
ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true, MasqueradeAll: true,
@ -448,9 +392,9 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "must be greater than 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ConfigSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than 0")},
}, },
{ "IPVS mode selected without providing required SyncPeriod": {
config: kubeproxyconfig.KubeProxyConfiguration{ config: kubeproxyconfig.KubeProxyConfiguration{
BindAddress: "192.168.59.103", BindAddress: "192.168.59.103",
HealthzBindAddress: "0.0.0.0:10256", HealthzBindAddress: "0.0.0.0:10256",
@ -472,295 +416,276 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
msg: "must be greater than 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeProxyIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0")},
}, },
} }
for _, errorCase := range errorCases { for _, testCase := range testCases {
if errs := Validate(&errorCase.config); len(errs) == 0 { errs := Validate(&testCase.config)
t.Errorf("expected failure for %s", errorCase.msg) if len(testCase.expectedErrs) != len(errs) {
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) }
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
}
} }
} }
} }
func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) { func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) {
valid := int32(5)
successCases := []kubeproxyconfig.KubeProxyIPTablesConfiguration{
{
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
{
MasqueradeBit: &valid,
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
}
newPath := field.NewPath("KubeProxyConfiguration") newPath := field.NewPath("KubeProxyConfiguration")
for _, successCase := range successCases {
if errs := validateKubeProxyIPTablesConfiguration(successCase, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
invalid := int32(-10) testCases := map[string]struct {
errorCases := []struct {
config kubeproxyconfig.KubeProxyIPTablesConfiguration config kubeproxyconfig.KubeProxyIPTablesConfiguration
msg string expectedErrs field.ErrorList
}{ }{
{ "valid iptables config": {
config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
expectedErrs: field.ErrorList{},
},
"valid custom MasqueradeBit": {
config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeBit: pointer.Int32Ptr(5),
MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
},
expectedErrs: field.ErrorList{},
},
"SyncPeriod must be > 0": {
config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeAll: true, MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, SyncPeriod: metav1.Duration{Duration: -5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
}, },
msg: "must be greater than 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"),
field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")},
}, },
{ "MinSyncPeriod must be > 0": {
config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeBit: &valid, MasqueradeBit: pointer.Int32Ptr(5),
MasqueradeAll: true, MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
}, },
msg: "must be greater than or equal to 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
}, },
{ "MasqueradeBit cannot be < 0": {
config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeBit: &invalid, MasqueradeBit: pointer.Int32Ptr(-10),
MasqueradeAll: true, MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
}, },
msg: "must be within the range [0, 31]", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MasqueradeBit"), -10, "must be within the range [0, 31]")},
}, },
// SyncPeriod must be >= MinSyncPeriod "SyncPeriod must be >= MinSyncPeriod": {
{
config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
MasqueradeBit: &valid, MasqueradeBit: pointer.Int32Ptr(5),
MasqueradeAll: true, MasqueradeAll: true,
SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, SyncPeriod: metav1.Duration{Duration: 1 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
}, },
msg: fmt.Sprintf("must be greater than or equal to %s", newPath.Child("KubeProxyIPTablesConfiguration").Child("MinSyncPeriod").String()), expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 5 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")},
}, },
} }
for _, errorCase := range errorCases { for _, testCase := range testCases {
if errs := validateKubeProxyIPTablesConfiguration(errorCase.config, newPath.Child("KubeProxyIPTablesConfiguration")); len(errs) == 0 { errs := validateKubeProxyIPTablesConfiguration(testCase.config, newPath.Child("KubeIPTablesConfiguration"))
t.Errorf("expected failure for %s", errorCase.msg) if len(testCase.expectedErrs) != len(errs) {
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) }
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
}
} }
} }
} }
func TestValidateKubeProxyIPVSConfiguration(t *testing.T) { func TestValidateKubeProxyIPVSConfiguration(t *testing.T) {
newPath := field.NewPath("KubeProxyConfiguration") newPath := field.NewPath("KubeProxyConfiguration")
testCases := []struct { testCases := map[string]struct {
config kubeproxyconfig.KubeProxyIPVSConfiguration config kubeproxyconfig.KubeProxyIPVSConfiguration
expectErr bool expectedErrs field.ErrorList
reason string
}{ }{
{ "SyncPeriod is not greater than 0": {
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, SyncPeriod: metav1.Duration{Duration: -5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
}, },
expectErr: true, expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"),
reason: "SyncPeriod must be greater than 0", field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
}, },
{ "SyncPeriod cannot be 0": {
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 0 * time.Second}, SyncPeriod: metav1.Duration{Duration: 0 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
}, },
expectErr: true, expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0"),
reason: "SyncPeriod must be greater than 0", field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 10 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
}, },
{ "MinSyncPeriod cannot be less than 0": {
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
}, },
expectErr: true, expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
reason: "MinSyncPeriod must be greater than or equal to 0",
}, },
{ "SyncPeriod must be greater than MinSyncPeriod": {
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, SyncPeriod: metav1.Duration{Duration: 1 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
}, },
expectErr: true, expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 5 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
reason: "SyncPeriod must be greater than or equal to MinSyncPeriod",
}, },
// SyncPeriod == MinSyncPeriod "SyncPeriod == MinSyncPeriod": {
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, SyncPeriod: metav1.Duration{Duration: 10 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
}, },
expectErr: false, expectedErrs: field.ErrorList{},
}, },
// SyncPeriod > MinSyncPeriod "SyncPeriod should be > MinSyncPeriod": {
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, SyncPeriod: metav1.Duration{Duration: 10 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
}, },
expectErr: false, expectedErrs: field.ErrorList{},
}, },
// SyncPeriod can be 0 "MinSyncPeriod can be 0": {
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second}, MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second},
}, },
expectErr: false, expectedErrs: field.ErrorList{},
}, },
// IPVS Timeout can be 0 "IPVS Timeout can be 0": {
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
TCPTimeout: metav1.Duration{Duration: 0 * time.Second}, TCPTimeout: metav1.Duration{Duration: 0 * time.Second},
TCPFinTimeout: metav1.Duration{Duration: 0 * time.Second}, TCPFinTimeout: metav1.Duration{Duration: 0 * time.Second},
UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, UDPTimeout: metav1.Duration{Duration: 0 * time.Second},
}, },
expectErr: false, expectedErrs: field.ErrorList{},
}, },
// IPVS Timeout > 0 "IPVS Timeout > 0": {
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
TCPTimeout: metav1.Duration{Duration: 1 * time.Second}, TCPTimeout: metav1.Duration{Duration: 1 * time.Second},
TCPFinTimeout: metav1.Duration{Duration: 2 * time.Second}, TCPFinTimeout: metav1.Duration{Duration: 2 * time.Second},
UDPTimeout: metav1.Duration{Duration: 3 * time.Second}, UDPTimeout: metav1.Duration{Duration: 3 * time.Second},
}, },
expectErr: false, expectedErrs: field.ErrorList{},
}, },
// TCPTimeout Timeout < 0 "TCP,TCPFin,UDP Timeouts < 0": {
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{ config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
TCPTimeout: metav1.Duration{Duration: -1 * time.Second}, TCPTimeout: metav1.Duration{Duration: -1 * time.Second},
}, UDPTimeout: metav1.Duration{Duration: -1 * time.Second},
expectErr: true,
reason: "TCPTimeout must be greater than or equal to 0",
},
// TCPFinTimeout Timeout < 0
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
TCPFinTimeout: metav1.Duration{Duration: -1 * time.Second}, TCPFinTimeout: metav1.Duration{Duration: -1 * time.Second},
}, },
expectErr: true, expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"),
reason: "TCPFinTimeout must be greater than or equal to 0", field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPFinTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"),
}, field.Invalid(newPath.Child("KubeIPVSConfiguration.UDPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
// UDPTimeout Timeout < 0
{
config: kubeproxyconfig.KubeProxyIPVSConfiguration{
SyncPeriod: metav1.Duration{Duration: 5 * time.Second},
UDPTimeout: metav1.Duration{Duration: -1 * time.Second},
},
expectErr: true,
reason: "UDPTimeout must be greater than or equal to 0",
}, },
} }
for _, testCase := range testCases {
for _, test := range testCases { errs := validateKubeProxyIPVSConfiguration(testCase.config, newPath.Child("KubeIPVSConfiguration"))
errs := validateKubeProxyIPVSConfiguration(test.config, newPath.Child("KubeProxyIPVSConfiguration")) if len(testCase.expectedErrs) != len(errs) {
if len(errs) == 0 && test.expectErr { t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
t.Errorf("Expect error, got nil, reason: %s", test.reason) }
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
} }
if len(errs) > 0 && !test.expectErr {
t.Errorf("Unexpected error: %v", errs)
} }
} }
} }
func TestValidateKubeProxyConntrackConfiguration(t *testing.T) { func TestValidateKubeProxyConntrackConfiguration(t *testing.T) {
successCases := []kubeproxyconfig.KubeProxyConntrackConfiguration{ newPath := field.NewPath("KubeProxyConfiguration")
{ testCases := map[string]struct {
config kubeproxyconfig.KubeProxyConntrackConfiguration
expectedErrs field.ErrorList
}{
"valid 5 second timeouts": {
config: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1), MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(1), Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
{ expectedErrs: field.ErrorList{},
MaxPerCore: pointer.Int32Ptr(0), },
Min: pointer.Int32Ptr(0), "valid duration equal to 0 second timeout": {
config: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second}, TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second},
}, },
} expectedErrs: field.ErrorList{},
newPath := field.NewPath("KubeProxyConfiguration") },
for _, successCase := range successCases { "invalid MaxPerCore < 0": {
if errs := validateKubeProxyConntrackConfiguration(successCase, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := []struct {
config kubeproxyconfig.KubeProxyConntrackConfiguration
msg string
}{
{
config: kubeproxyconfig.KubeProxyConntrackConfiguration{ config: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(-1), MaxPerCore: pointer.Int32Ptr(-1),
Min: pointer.Int32Ptr(1), Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
msg: "must be greater than or equal to 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.MaxPerCore"), -1, "must be greater than or equal to 0")},
}, },
{ "invalid minimum < 0": {
config: kubeproxyconfig.KubeProxyConntrackConfiguration{ config: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1), MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(-1), Min: pointer.Int32Ptr(-1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
msg: "must be greater than or equal to 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.Min"), -1, "must be greater than or equal to 0")},
}, },
{ "invalid EstablishedTimeout < 0": {
config: kubeproxyconfig.KubeProxyConntrackConfiguration{ config: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1), MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(3), Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second}, TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second},
}, },
msg: "must be greater than or equal to 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPEstablishedTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
}, },
{ "invalid CloseWaitTimeout < 0": {
config: kubeproxyconfig.KubeProxyConntrackConfiguration{ config: kubeproxyconfig.KubeProxyConntrackConfiguration{
MaxPerCore: pointer.Int32Ptr(1), MaxPerCore: pointer.Int32Ptr(1),
Min: pointer.Int32Ptr(3), Min: pointer.Int32Ptr(1),
TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
TCPCloseWaitTimeout: &metav1.Duration{Duration: -5 * time.Second}, TCPCloseWaitTimeout: &metav1.Duration{Duration: -5 * time.Second},
}, },
msg: "must be greater than or equal to 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPCloseWaitTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
}, },
} }
for _, errorCase := range errorCases { for _, testCase := range testCases {
if errs := validateKubeProxyConntrackConfiguration(errorCase.config, newPath.Child("KubeProxyConntrackConfiguration")); len(errs) == 0 { errs := validateKubeProxyConntrackConfiguration(testCase.config, newPath.Child("KubeConntrackConfiguration"))
t.Errorf("expected failure for %s", errorCase.msg) if len(testCase.expectedErrs) != len(errs) {
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) }
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
}
} }
} }
} }
func TestValidateProxyMode(t *testing.T) { func TestValidateProxyMode(t *testing.T) {
newPath := field.NewPath("KubeProxyConfiguration") newPath := field.NewPath("KubeProxyConfiguration")
successCases := []kubeproxyconfig.ProxyMode{ successCases := []kubeproxyconfig.ProxyMode{""}
kubeproxyconfig.ProxyModeUserspace,
kubeproxyconfig.ProxyMode(""),
}
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace) successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace)
@ -774,21 +699,32 @@ func TestValidateProxyMode(t *testing.T) {
} }
} }
errorCases := []struct { testCases := map[string]struct {
mode kubeproxyconfig.ProxyMode mode kubeproxyconfig.ProxyMode
msg string expectedErrs field.ErrorList
}{ }{
{ "valid Userspace mode": {
mode: kubeproxyconfig.ProxyModeUserspace,
expectedErrs: field.ErrorList{},
},
"blank mode should default": {
mode: kubeproxyconfig.ProxyMode(""),
expectedErrs: field.ErrorList{},
},
"invalid mode non-existent": {
mode: kubeproxyconfig.ProxyMode("non-existing"), mode: kubeproxyconfig.ProxyMode("non-existing"),
msg: "or blank (blank means the", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", "must be iptables,ipvs,userspace or blank (blank means the best-available proxy [currently iptables])")},
}, },
} }
for _, testCase := range testCases {
for _, errorCase := range errorCases { errs := validateProxyMode(testCase.mode, newPath)
if errs := validateProxyMode(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 { if len(testCase.expectedErrs) != len(errs) {
t.Errorf("expected failure %s for %v", errorCase.msg, errorCase.mode) t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { }
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Errorf("Expected error: %s, got %v", testCase.expectedErrs[i], err.Error())
}
} }
} }
} }
@ -796,36 +732,33 @@ func TestValidateProxyMode(t *testing.T) {
func TestValidateClientConnectionConfiguration(t *testing.T) { func TestValidateClientConnectionConfiguration(t *testing.T) {
newPath := field.NewPath("KubeProxyConfiguration") newPath := field.NewPath("KubeProxyConfiguration")
successCases := []componentbaseconfig.ClientConnectionConfiguration{ testCases := map[string]struct {
{
Burst: 0,
},
{
Burst: 5,
},
}
for _, successCase := range successCases {
if errs := validateClientConnectionConfiguration(successCase, newPath.Child("Burst")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := []struct {
ccc componentbaseconfig.ClientConnectionConfiguration ccc componentbaseconfig.ClientConnectionConfiguration
msg string expectedErrs field.ErrorList
}{ }{
{ "successful 0 value": {
ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: 0},
expectedErrs: field.ErrorList{},
},
"successful 5 value": {
ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: 5},
expectedErrs: field.ErrorList{},
},
"burst < 0": {
ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: -5}, ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: -5},
msg: "must be greater than or equal to 0", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("Burst"), -5, "must be greater than or equal to 0")},
}, },
} }
for _, errorCase := range errorCases { for _, testCase := range testCases {
if errs := validateClientConnectionConfiguration(errorCase.ccc, newPath.Child("Burst")); len(errs) == 0 { errs := validateClientConnectionConfiguration(testCase.ccc, newPath)
t.Errorf("expected failure for %s", errorCase.msg) if len(testCase.expectedErrs) != len(errs) {
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) }
for i, err := range errs {
if err.Error() != testCase.expectedErrs[i].Error() {
t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
}
} }
} }
} }
@ -845,37 +778,41 @@ func TestValidateHostPort(t *testing.T) {
} }
} }
errorCases := []struct { errorCases := map[string]struct {
ccc string ip string
msg string expectedErrs field.ErrorList
}{ }{
{ "missing port": {
ccc: "10.10.10.10", ip: "10.10.10.10",
msg: "must be IP:port", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "10.10.10.10", "must be IP:port")},
}, },
{ "digits outside of 1-255": {
ccc: "123.456.789.10:12345", ip: "123.456.789.10:12345",
msg: "must be a valid IP", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "123.456.789.10", "must be a valid IP")},
}, },
{ "invalid named-port": {
ccc: "10.10.10.10:foo", ip: "10.10.10.10:foo",
msg: "must be a valid port", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "foo", "must be a valid port")},
}, },
{ "port cannot be 0": {
ccc: "10.10.10.10:0", ip: "10.10.10.10:0",
msg: "must be a valid port", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0", "must be a valid port")},
}, },
{ "port is greater than allowed range": {
ccc: "10.10.10.10:65536", ip: "10.10.10.10:65536",
msg: "must be a valid port", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "65536", "must be a valid port")},
}, },
} }
for _, errorCase := range errorCases { for _, errorCase := range errorCases {
if errs := validateHostPort(errorCase.ccc, newPath.Child("HealthzBindAddress")); len(errs) == 0 { errs := validateHostPort(errorCase.ip, newPath.Child("HealthzBindAddress"))
t.Errorf("expected failure for %s", errorCase.msg) if len(errorCase.expectedErrs) != len(errs) {
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { t.Fatalf("Expected %d errors, got %d errors: %v", len(errorCase.expectedErrs), len(errs), errs)
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) }
for i, err := range errs {
if err.Error() != errorCase.expectedErrs[i].Error() {
t.Errorf("Expected error: %s, got %s", errorCase.expectedErrs[i], err.Error())
}
} }
} }
} }
@ -903,21 +840,25 @@ func TestValidateIPVSSchedulerMethod(t *testing.T) {
} }
} }
errorCases := []struct { errorCases := map[string]struct {
mode kubeproxyconfig.IPVSSchedulerMethod mode kubeproxyconfig.IPVSSchedulerMethod
msg string expectedErrs field.ErrorList
}{ }{
{ "non-existent scheduler method": {
mode: kubeproxyconfig.IPVSSchedulerMethod("non-existing"), mode: kubeproxyconfig.IPVSSchedulerMethod("non-existing"),
msg: "blank means the default algorithm method (currently rr)", expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode.Scheduler"), "non-existing", "must be in [rr wrr lc wlc lblc lblcr sh dh sed nq ], blank means the default algorithm method (currently rr)")},
}, },
} }
for _, errorCase := range errorCases { for _, errorCase := range errorCases {
if errs := validateIPVSSchedulerMethod(errorCase.mode, newPath.Child("ProxyMode")); len(errs) == 0 { errs := validateIPVSSchedulerMethod(errorCase.mode, newPath.Child("ProxyMode"))
t.Errorf("expected failure for %s", errorCase.msg) if len(errorCase.expectedErrs) != len(errs) {
} else if !strings.Contains(errs[0].Error(), errorCase.msg) { t.Fatalf("Expected %d errors, got %d errors: %v", len(errorCase.expectedErrs), len(errs), errs)
t.Errorf("unexpected error: %v, expected: %s", errs[0], errorCase.msg) }
for i, err := range errs {
if err.Error() != errorCase.expectedErrs[i].Error() {
t.Fatalf("Expected error: %s, got %s", errorCase.expectedErrs[i], err.Error())
}
} }
} }
} }