k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/proxy/apis/config/validation/validation_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validation 18 19 import ( 20 "runtime" 21 "testing" 22 "time" 23 24 "github.com/stretchr/testify/assert" 25 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/validation/field" 28 componentbaseconfig "k8s.io/component-base/config" 29 logsapi "k8s.io/component-base/logs/api/v1" 30 kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" 31 "k8s.io/utils/ptr" 32 ) 33 34 func TestValidateKubeProxyConfiguration(t *testing.T) { 35 baseConfig := &kubeproxyconfig.KubeProxyConfiguration{ 36 BindAddress: "192.168.59.103", 37 HealthzBindAddress: "0.0.0.0:10256", 38 MetricsBindAddress: "127.0.0.1:10249", 39 ClusterCIDR: "192.168.59.0/24", 40 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 41 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 42 MasqueradeAll: true, 43 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 44 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 45 }, 46 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 47 MaxPerCore: ptr.To[int32](1), 48 Min: ptr.To[int32](1), 49 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 50 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 51 }, 52 Logging: logsapi.LoggingConfiguration{ 53 Format: "text", 54 }, 55 } 56 newPath := field.NewPath("KubeProxyConfiguration") 57 58 for name, testCase := range map[string]struct { 59 mutateConfigFunc func(*kubeproxyconfig.KubeProxyConfiguration) 60 expectedErrs field.ErrorList 61 }{ 62 "basic config, unspecified Mode": { 63 mutateConfigFunc: func(_ *kubeproxyconfig.KubeProxyConfiguration) {}, 64 }, 65 "Mode specified, extra mode-specific configs": { 66 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 67 if runtime.GOOS == "windows" { 68 config.Mode = kubeproxyconfig.ProxyModeKernelspace 69 } else { 70 config.Mode = kubeproxyconfig.ProxyModeIPVS 71 config.IPVS = kubeproxyconfig.KubeProxyIPVSConfiguration{ 72 SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 73 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 74 } 75 } 76 }, 77 }, 78 "empty HealthzBindAddress": { 79 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 80 config.HealthzBindAddress = "" 81 }, 82 }, 83 "IPv6": { 84 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 85 config.BindAddress = "fd00:192:168:59::103" 86 config.HealthzBindAddress = "" 87 config.MetricsBindAddress = "[::1]:10249" 88 config.ClusterCIDR = "fd00:192:168:59::/64" 89 }, 90 }, 91 "alternate healthz port": { 92 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 93 config.HealthzBindAddress = "0.0.0.0:12345" 94 }, 95 }, 96 "ClusterCIDR is wrong IP family": { 97 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 98 config.ClusterCIDR = "fd00:192:168::/64" 99 }, 100 }, 101 "ClusterCIDR is dual-stack": { 102 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 103 config.ClusterCIDR = "192.168.59.0/24,fd00:192:168::/64" 104 }, 105 }, 106 "LocalModeInterfaceNamePrefix": { 107 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 108 config.DetectLocalMode = kubeproxyconfig.LocalModeInterfaceNamePrefix 109 config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{ 110 InterfaceNamePrefix: "vethabcde", 111 } 112 }, 113 }, 114 "LocalModeBridgeInterface": { 115 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 116 config.DetectLocalMode = kubeproxyconfig.LocalModeBridgeInterface 117 config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{ 118 BridgeInterface: "avz", 119 } 120 }, 121 }, 122 "invalid BindAddress": { 123 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 124 config.BindAddress = "10.10.12.11:2000" 125 }, 126 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("BindAddress"), "10.10.12.11:2000", "not a valid textual representation of an IP address")}, 127 }, 128 "invalid HealthzBindAddress": { 129 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 130 config.HealthzBindAddress = "0.0.0.0" 131 }, 132 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0.0.0.0", "must be IP:port")}, 133 }, 134 "invalid MetricsBindAddress": { 135 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 136 config.MetricsBindAddress = "127.0.0.1" 137 }, 138 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("MetricsBindAddress"), "127.0.0.1", "must be IP:port")}, 139 }, 140 "ClusterCIDR missing subset range": { 141 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 142 config.ClusterCIDR = "192.168.59.0" 143 }, 144 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)")}, 145 }, 146 "Invalid number of ClusterCIDRs": { 147 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 148 config.ClusterCIDR = "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16" 149 }, 150 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)")}, 151 }, 152 "ConfigSyncPeriod must be > 0": { 153 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 154 config.ConfigSyncPeriod = metav1.Duration{Duration: -1 * time.Second} 155 }, 156 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ConfigSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than 0")}, 157 }, 158 "IPVS mode selected without providing required SyncPeriod": { 159 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 160 config.Mode = kubeproxyconfig.ProxyModeIPVS 161 }, 162 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeProxyIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0")}, 163 }, 164 "interfacePrefix is empty": { 165 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 166 config.DetectLocalMode = kubeproxyconfig.LocalModeInterfaceNamePrefix 167 config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{ 168 InterfaceNamePrefix: "", 169 } 170 }, 171 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfacePrefix"), "", "must not be empty")}, 172 }, 173 "bridgeInterfaceName is empty": { 174 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 175 config.DetectLocalMode = kubeproxyconfig.LocalModeBridgeInterface 176 config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{ 177 InterfaceNamePrefix: "eth0", // we won't care about prefix since mode is not prefix 178 } 179 }, 180 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfaceName"), "", "must not be empty")}, 181 }, 182 "invalid DetectLocalMode": { 183 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 184 config.DetectLocalMode = "Guess" 185 }, 186 expectedErrs: field.ErrorList{field.NotSupported(newPath.Child("DetectLocalMode"), "Guess", []string{"ClusterCIDR", "NodeCIDR", "BridgeInterface", "InterfaceNamePrefix", ""})}, 187 }, 188 "invalid logging format": { 189 mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) { 190 config.Logging = logsapi.LoggingConfiguration{ 191 Format: "unsupported format", 192 } 193 }, 194 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("logging.format"), "unsupported format", "Unsupported log format")}, 195 }, 196 } { 197 t.Run(name, func(t *testing.T) { 198 config := baseConfig.DeepCopy() 199 testCase.mutateConfigFunc(config) 200 errs := Validate(config) 201 if len(testCase.expectedErrs) == 0 { 202 assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors") 203 } else { 204 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 205 } 206 }) 207 } 208 } 209 210 func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) { 211 newPath := field.NewPath("KubeProxyConfiguration") 212 213 for name, testCase := range map[string]struct { 214 config kubeproxyconfig.KubeProxyIPTablesConfiguration 215 expectedErrs field.ErrorList 216 }{ 217 "valid iptables config": { 218 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 219 MasqueradeAll: true, 220 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 221 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 222 }, 223 expectedErrs: field.ErrorList{}, 224 }, 225 "valid custom MasqueradeBit": { 226 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 227 MasqueradeBit: ptr.To[int32](5), 228 MasqueradeAll: true, 229 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 230 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 231 }, 232 expectedErrs: field.ErrorList{}, 233 }, 234 "SyncPeriod must be > 0": { 235 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 236 MasqueradeAll: true, 237 SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, 238 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 239 }, 240 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"), 241 field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")}, 242 }, 243 "MinSyncPeriod must be > 0": { 244 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 245 MasqueradeBit: ptr.To[int32](5), 246 MasqueradeAll: true, 247 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 248 MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, 249 }, 250 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")}, 251 }, 252 "MasqueradeBit cannot be < 0": { 253 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 254 MasqueradeBit: ptr.To[int32](-10), 255 MasqueradeAll: true, 256 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 257 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 258 }, 259 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MasqueradeBit"), ptr.To[int32](-10), "must be within the range [0, 31]")}, 260 }, 261 "SyncPeriod must be >= MinSyncPeriod": { 262 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 263 MasqueradeBit: ptr.To[int32](5), 264 MasqueradeAll: true, 265 SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 266 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 267 }, 268 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")}, 269 }, 270 } { 271 t.Run(name, func(t *testing.T) { 272 errs := validateKubeProxyIPTablesConfiguration(testCase.config, newPath.Child("KubeIPTablesConfiguration")) 273 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 274 }) 275 } 276 } 277 278 func TestValidateKubeProxyIPVSConfiguration(t *testing.T) { 279 newPath := field.NewPath("KubeProxyConfiguration") 280 for name, testCase := range map[string]struct { 281 config kubeproxyconfig.KubeProxyIPVSConfiguration 282 expectedErrs field.ErrorList 283 }{ 284 "SyncPeriod is not greater than 0": { 285 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 286 SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, 287 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 288 }, 289 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"), 290 field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")}, 291 }, 292 "SyncPeriod cannot be 0": { 293 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 294 SyncPeriod: metav1.Duration{Duration: 0 * time.Second}, 295 MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 296 }, 297 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0"), 298 field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 10 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")}, 299 }, 300 "MinSyncPeriod cannot be less than 0": { 301 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 302 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 303 MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, 304 }, 305 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")}, 306 }, 307 "SyncPeriod must be greater than MinSyncPeriod": { 308 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 309 SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 310 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 311 }, 312 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")}, 313 }, 314 "SyncPeriod == MinSyncPeriod": { 315 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 316 SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 317 MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 318 }, 319 expectedErrs: field.ErrorList{}, 320 }, 321 "SyncPeriod should be > MinSyncPeriod": { 322 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 323 SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 324 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 325 }, 326 expectedErrs: field.ErrorList{}, 327 }, 328 "MinSyncPeriod can be 0": { 329 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 330 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 331 MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second}, 332 }, 333 expectedErrs: field.ErrorList{}, 334 }, 335 "IPVS Timeout can be 0": { 336 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 337 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 338 TCPTimeout: metav1.Duration{Duration: 0 * time.Second}, 339 TCPFinTimeout: metav1.Duration{Duration: 0 * time.Second}, 340 UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, 341 }, 342 expectedErrs: field.ErrorList{}, 343 }, 344 "IPVS Timeout > 0": { 345 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 346 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 347 TCPTimeout: metav1.Duration{Duration: 1 * time.Second}, 348 TCPFinTimeout: metav1.Duration{Duration: 2 * time.Second}, 349 UDPTimeout: metav1.Duration{Duration: 3 * time.Second}, 350 }, 351 expectedErrs: field.ErrorList{}, 352 }, 353 "TCP,TCPFin,UDP Timeouts < 0": { 354 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 355 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 356 TCPTimeout: metav1.Duration{Duration: -1 * time.Second}, 357 UDPTimeout: metav1.Duration{Duration: -1 * time.Second}, 358 TCPFinTimeout: metav1.Duration{Duration: -1 * time.Second}, 359 }, 360 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"), 361 field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPFinTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"), 362 field.Invalid(newPath.Child("KubeIPVSConfiguration.UDPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")}, 363 }, 364 } { 365 t.Run(name, func(t *testing.T) { 366 errs := validateKubeProxyIPVSConfiguration(testCase.config, newPath.Child("KubeIPVSConfiguration")) 367 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 368 }) 369 } 370 } 371 372 func TestValidateKubeProxyConntrackConfiguration(t *testing.T) { 373 newPath := field.NewPath("KubeProxyConfiguration") 374 for name, testCase := range map[string]struct { 375 config kubeproxyconfig.KubeProxyConntrackConfiguration 376 expectedErrs field.ErrorList 377 }{ 378 "valid 5 second timeouts": { 379 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 380 MaxPerCore: ptr.To[int32](1), 381 Min: ptr.To[int32](1), 382 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 383 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 384 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 385 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 386 }, 387 expectedErrs: field.ErrorList{}, 388 }, 389 "valid duration equal to 0 second timeout": { 390 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 391 MaxPerCore: ptr.To[int32](1), 392 Min: ptr.To[int32](1), 393 TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second}, 394 TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second}, 395 UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, 396 UDPStreamTimeout: metav1.Duration{Duration: 0 * time.Second}, 397 }, 398 expectedErrs: field.ErrorList{}, 399 }, 400 "invalid MaxPerCore < 0": { 401 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 402 MaxPerCore: ptr.To[int32](-1), 403 Min: ptr.To[int32](1), 404 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 405 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 406 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 407 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 408 }, 409 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.MaxPerCore"), ptr.To[int32](-1), "must be greater than or equal to 0")}, 410 }, 411 "invalid minimum < 0": { 412 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 413 MaxPerCore: ptr.To[int32](1), 414 Min: ptr.To[int32](-1), 415 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 416 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 417 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 418 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 419 }, 420 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.Min"), ptr.To[int32](-1), "must be greater than or equal to 0")}, 421 }, 422 "invalid TCPEstablishedTimeout < 0": { 423 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 424 MaxPerCore: ptr.To[int32](1), 425 Min: ptr.To[int32](1), 426 TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second}, 427 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 428 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 429 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 430 }, 431 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPEstablishedTimeout"), &metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 432 }, 433 "invalid TCPCloseWaitTimeout < 0": { 434 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 435 MaxPerCore: ptr.To[int32](1), 436 Min: ptr.To[int32](1), 437 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 438 TCPCloseWaitTimeout: &metav1.Duration{Duration: -5 * time.Second}, 439 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 440 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 441 }, 442 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPCloseWaitTimeout"), &metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 443 }, 444 "invalid UDPTimeout < 0": { 445 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 446 MaxPerCore: ptr.To[int32](1), 447 Min: ptr.To[int32](1), 448 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 449 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 450 UDPTimeout: metav1.Duration{Duration: -5 * time.Second}, 451 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 452 }, 453 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 454 }, 455 "invalid UDPStreamTimeout < 0": { 456 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 457 MaxPerCore: ptr.To[int32](1), 458 Min: ptr.To[int32](1), 459 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 460 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 461 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 462 UDPStreamTimeout: metav1.Duration{Duration: -5 * time.Second}, 463 }, 464 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPStreamTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 465 }, 466 } { 467 t.Run(name, func(t *testing.T) { 468 errs := validateKubeProxyConntrackConfiguration(testCase.config, newPath.Child("KubeConntrackConfiguration")) 469 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 470 }) 471 } 472 } 473 474 func TestValidateProxyMode(t *testing.T) { 475 if runtime.GOOS == "windows" { 476 testValidateProxyModeWindows(t) 477 } else { 478 testValidateProxyModeLinux(t) 479 } 480 } 481 482 func testValidateProxyModeLinux(t *testing.T) { 483 newPath := field.NewPath("KubeProxyConfiguration") 484 for name, testCase := range map[string]struct { 485 mode kubeproxyconfig.ProxyMode 486 expectedErrs field.ErrorList 487 }{ 488 "blank mode should default": { 489 mode: kubeproxyconfig.ProxyMode(""), 490 }, 491 "iptables is allowed": { 492 mode: kubeproxyconfig.ProxyModeIPTables, 493 }, 494 "ipvs is allowed": { 495 mode: kubeproxyconfig.ProxyModeIPVS, 496 }, 497 "nftables is allowed": { 498 mode: kubeproxyconfig.ProxyModeNFTables, 499 }, 500 "winkernel is not allowed": { 501 mode: kubeproxyconfig.ProxyModeKernelspace, 502 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "kernelspace", "must be iptables, ipvs, nftables or blank (blank means the best-available proxy [currently iptables])")}, 503 }, 504 "invalid mode non-existent": { 505 mode: kubeproxyconfig.ProxyMode("non-existing"), 506 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", "must be iptables, ipvs, nftables or blank (blank means the best-available proxy [currently iptables])")}, 507 }, 508 } { 509 t.Run(name, func(t *testing.T) { 510 errs := validateProxyMode(testCase.mode, newPath) 511 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 512 }) 513 } 514 } 515 516 func testValidateProxyModeWindows(t *testing.T) { 517 // TODO: remove skip once the test has been fixed. 518 if runtime.GOOS == "windows" { 519 t.Skip("Skipping failing test on Windows.") 520 } 521 newPath := field.NewPath("KubeProxyConfiguration") 522 for name, testCase := range map[string]struct { 523 mode kubeproxyconfig.ProxyMode 524 expectedErrs field.ErrorList 525 }{ 526 "blank mode should default": { 527 mode: kubeproxyconfig.ProxyMode(""), 528 }, 529 "winkernel is allowed": { 530 mode: kubeproxyconfig.ProxyModeKernelspace, 531 }, 532 "iptables is not allowed": { 533 mode: kubeproxyconfig.ProxyModeIPTables, 534 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "iptables", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")}, 535 }, 536 "ipvs is not allowed": { 537 mode: kubeproxyconfig.ProxyModeIPVS, 538 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "ipvs", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")}, 539 }, 540 "nftables is not allowed": { 541 mode: kubeproxyconfig.ProxyModeNFTables, 542 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "nftables", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")}, 543 }, 544 "invalid mode non-existent": { 545 mode: kubeproxyconfig.ProxyMode("non-existing"), 546 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")}, 547 }, 548 } { 549 t.Run(name, func(t *testing.T) { 550 errs := validateProxyMode(testCase.mode, newPath) 551 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 552 }) 553 } 554 } 555 556 func TestValidateClientConnectionConfiguration(t *testing.T) { 557 newPath := field.NewPath("KubeProxyConfiguration") 558 for name, testCase := range map[string]struct { 559 ccc componentbaseconfig.ClientConnectionConfiguration 560 expectedErrs field.ErrorList 561 }{ 562 "successful 0 value": { 563 ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: 0}, 564 expectedErrs: field.ErrorList{}, 565 }, 566 "successful 5 value": { 567 ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: 5}, 568 expectedErrs: field.ErrorList{}, 569 }, 570 "burst < 0": { 571 ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: -5}, 572 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("Burst"), int64(-5), "must be greater than or equal to 0")}, 573 }, 574 } { 575 t.Run(name, func(t *testing.T) { 576 errs := validateClientConnectionConfiguration(testCase.ccc, newPath) 577 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 578 }) 579 } 580 } 581 582 func TestValidateHostPort(t *testing.T) { 583 newPath := field.NewPath("KubeProxyConfiguration") 584 for name, testCase := range map[string]struct { 585 ip string 586 expectedErrs field.ErrorList 587 }{ 588 "all IPs": { 589 ip: "0.0.0.0:10256", 590 }, 591 "localhost": { 592 ip: "127.0.0.1:10256", 593 }, 594 "specific IP": { 595 ip: "10.10.10.10:10256", 596 }, 597 "missing port": { 598 ip: "10.10.10.10", 599 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "10.10.10.10", "must be IP:port")}, 600 }, 601 "digits outside of 1-255": { 602 ip: "123.456.789.10:12345", 603 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "123.456.789.10", "must be a valid IP")}, 604 }, 605 "invalid named-port": { 606 ip: "10.10.10.10:foo", 607 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "foo", "must be a valid port")}, 608 }, 609 "port cannot be 0": { 610 ip: "10.10.10.10:0", 611 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0", "must be a valid port")}, 612 }, 613 "port is greater than allowed range": { 614 ip: "10.10.10.10:65536", 615 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "65536", "must be a valid port")}, 616 }, 617 } { 618 t.Run(name, func(t *testing.T) { 619 errs := validateHostPort(testCase.ip, newPath.Child("HealthzBindAddress")) 620 if len(testCase.expectedErrs) == 0 { 621 assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors") 622 } else { 623 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 624 } 625 }) 626 } 627 } 628 629 func TestValidateKubeProxyNodePortAddress(t *testing.T) { 630 newPath := field.NewPath("KubeProxyConfiguration") 631 for name, testCase := range map[string]struct { 632 addresses []string 633 expectedErrs field.ErrorList 634 }{ 635 "no addresses": { 636 addresses: []string{}, 637 }, 638 "valid 1": { 639 addresses: []string{"127.0.0.0/8"}, 640 }, 641 "valid 2": { 642 addresses: []string{"0.0.0.0/0"}, 643 }, 644 "valid 3": { 645 addresses: []string{"::/0"}, 646 }, 647 "valid 4": { 648 addresses: []string{"127.0.0.1/32", "1.2.3.0/24"}, 649 }, 650 "valid 5": { 651 addresses: []string{"127.0.0.1/32"}, 652 }, 653 "valid 6": { 654 addresses: []string{"::1/128"}, 655 }, 656 "valid 7": { 657 addresses: []string{"1.2.3.4/32"}, 658 }, 659 "valid 8": { 660 addresses: []string{"10.20.30.0/24"}, 661 }, 662 "valid 9": { 663 addresses: []string{"10.20.0.0/16", "100.200.0.0/16"}, 664 }, 665 "valid 10": { 666 addresses: []string{"10.0.0.0/8"}, 667 }, 668 "valid 11": { 669 addresses: []string{"2001:db8::/32"}, 670 }, 671 "primary": { 672 addresses: []string{kubeproxyconfig.NodePortAddressesPrimary}, 673 }, 674 "invalid foo address": { 675 addresses: []string{"foo"}, 676 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "foo", "must be a valid CIDR")}, 677 }, 678 "invalid octet address": { 679 addresses: []string{"10.0.0.0/0", "1.2.3"}, 680 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "1.2.3", "must be a valid CIDR")}, 681 }, 682 "address cannot be 0": { 683 addresses: []string{"127.0.0.1/32", "0", "1.2.3.0/24"}, 684 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "0", "must be a valid CIDR")}, 685 }, 686 "address missing subnet range": { 687 addresses: []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"}, 688 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "10.20.30.40", "must be a valid CIDR")}, 689 }, 690 "missing ipv6 subnet ranges": { 691 addresses: []string{"::0", "::1", "2001:db8::/32"}, 692 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "::0", "must be a valid CIDR"), 693 field.Invalid(newPath.Child("NodePortAddresses[1]"), "::1", "must be a valid CIDR")}, 694 }, 695 "invalid ipv6 ip format": { 696 addresses: []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"}, 697 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[2]"), "2001:db8:xyz/64", "must be a valid CIDR")}, 698 }, 699 "invalid primary/CIDR mix 1": { 700 addresses: []string{"primary", "127.0.0.1/32"}, 701 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "primary", "can't use both 'primary' and CIDRs")}, 702 }, 703 "invalid primary/CIDR mix 2": { 704 addresses: []string{"127.0.0.1/32", "primary"}, 705 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "primary", "can't use both 'primary' and CIDRs")}, 706 }, 707 } { 708 t.Run(name, func(t *testing.T) { 709 errs := validateKubeProxyNodePortAddress(testCase.addresses, newPath.Child("NodePortAddresses")) 710 if len(testCase.expectedErrs) == 0 { 711 assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors") 712 } else { 713 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 714 } 715 }) 716 } 717 } 718 719 func TestValidateKubeProxyExcludeCIDRs(t *testing.T) { 720 newPath := field.NewPath("KubeProxyConfiguration") 721 for name, testCase := range map[string]struct { 722 addresses []string 723 expectedErrs field.ErrorList 724 }{ 725 "no cidrs": { 726 addresses: []string{}, 727 }, 728 "valid 1": { 729 addresses: []string{"127.0.0.0/8"}, 730 }, 731 "valid 2": { 732 addresses: []string{"0.0.0.0/0"}, 733 }, 734 "valid 3": { 735 addresses: []string{"::/0"}, 736 }, 737 "valid 4": { 738 addresses: []string{"127.0.0.1/32", "1.2.3.0/24"}, 739 }, 740 "valid 5": { 741 addresses: []string{"127.0.0.1/32"}, 742 }, 743 "valid 6": { 744 addresses: []string{"::1/128"}, 745 }, 746 "valid 7": { 747 addresses: []string{"1.2.3.4/32"}, 748 }, 749 "valid 8": { 750 addresses: []string{"10.20.30.0/24"}, 751 }, 752 "valid 9": { 753 addresses: []string{"10.20.0.0/16", "100.200.0.0/16"}, 754 }, 755 "valid 10": { 756 addresses: []string{"10.0.0.0/8"}, 757 }, 758 "valid 11": { 759 addresses: []string{"2001:db8::/32"}, 760 }, 761 "invalid foo address": { 762 addresses: []string{"foo"}, 763 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "foo", "must be a valid CIDR")}, 764 }, 765 "invalid octet address": { 766 addresses: []string{"10.0.0.0/0", "1.2.3"}, 767 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "1.2.3", "must be a valid CIDR")}, 768 }, 769 "address cannot be 0": { 770 addresses: []string{"127.0.0.1/32", "0", "1.2.3.0/24"}, 771 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "0", "must be a valid CIDR")}, 772 }, 773 "address missing subnet range": { 774 addresses: []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"}, 775 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "10.20.30.40", "must be a valid CIDR")}, 776 }, 777 "missing ipv6 subnet ranges": { 778 addresses: []string{"::0", "::1", "2001:db8::/32"}, 779 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "::0", "must be a valid CIDR"), 780 field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "::1", "must be a valid CIDR")}, 781 }, 782 "invalid ipv6 ip format": { 783 addresses: []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"}, 784 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[2]"), "2001:db8:xyz/64", "must be a valid CIDR")}, 785 }, 786 } { 787 t.Run(name, func(t *testing.T) { 788 errs := validateIPVSExcludeCIDRs(testCase.addresses, newPath.Child("ExcludeCIDRS")) 789 if len(testCase.expectedErrs) == 0 { 790 assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors") 791 } else { 792 assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors") 793 } 794 }) 795 } 796 }