k8s.io/kubernetes@v1.29.3/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 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 componentbaseconfig "k8s.io/component-base/config" 27 logsapi "k8s.io/component-base/logs/api/v1" 28 kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" 29 30 "k8s.io/utils/ptr" 31 ) 32 33 func TestValidateKubeProxyConfiguration(t *testing.T) { 34 var proxyMode kubeproxyconfig.ProxyMode 35 if runtime.GOOS == "windows" { 36 proxyMode = kubeproxyconfig.ProxyModeKernelspace 37 } else { 38 proxyMode = kubeproxyconfig.ProxyModeIPVS 39 } 40 successCases := []kubeproxyconfig.KubeProxyConfiguration{{ 41 BindAddress: "192.168.59.103", 42 HealthzBindAddress: "0.0.0.0:10256", 43 MetricsBindAddress: "127.0.0.1:10249", 44 ClusterCIDR: "192.168.59.0/24", 45 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 46 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 47 MasqueradeAll: true, 48 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 49 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 50 }, 51 Mode: proxyMode, 52 IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ 53 SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 54 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 55 }, 56 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 57 MaxPerCore: ptr.To[int32](1), 58 Min: ptr.To[int32](1), 59 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 60 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 61 }, 62 Logging: logsapi.LoggingConfiguration{ 63 Format: "text", 64 }, 65 }, { 66 BindAddress: "192.168.59.103", 67 HealthzBindAddress: "0.0.0.0:10256", 68 MetricsBindAddress: "127.0.0.1:10249", 69 ClusterCIDR: "192.168.59.0/24", 70 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 71 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 72 MasqueradeAll: true, 73 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 74 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 75 }, 76 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 77 MaxPerCore: ptr.To[int32](1), 78 Min: ptr.To[int32](1), 79 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 80 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 81 }, 82 Logging: logsapi.LoggingConfiguration{ 83 Format: "text", 84 }, 85 }, { 86 BindAddress: "192.168.59.103", 87 HealthzBindAddress: "", 88 MetricsBindAddress: "127.0.0.1:10249", 89 ClusterCIDR: "192.168.59.0/24", 90 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 91 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 92 MasqueradeAll: true, 93 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 94 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 95 }, 96 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 97 MaxPerCore: ptr.To[int32](1), 98 Min: ptr.To[int32](1), 99 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 100 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 101 }, 102 Logging: logsapi.LoggingConfiguration{ 103 Format: "text", 104 }, 105 }, { 106 BindAddress: "fd00:192:168:59::103", 107 HealthzBindAddress: "", 108 MetricsBindAddress: "[::1]:10249", 109 ClusterCIDR: "fd00:192:168:59::/64", 110 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 111 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 112 MasqueradeAll: true, 113 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 114 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 115 }, 116 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 117 MaxPerCore: ptr.To[int32](1), 118 Min: ptr.To[int32](1), 119 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 120 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 121 }, 122 Logging: logsapi.LoggingConfiguration{ 123 Format: "text", 124 }, 125 }, { 126 BindAddress: "10.10.12.11", 127 HealthzBindAddress: "0.0.0.0:12345", 128 MetricsBindAddress: "127.0.0.1:10249", 129 ClusterCIDR: "192.168.59.0/24", 130 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 131 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 132 MasqueradeAll: true, 133 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 134 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 135 }, 136 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 137 MaxPerCore: ptr.To[int32](1), 138 Min: ptr.To[int32](1), 139 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 140 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 141 }, 142 Logging: logsapi.LoggingConfiguration{ 143 Format: "text", 144 }, 145 }, { 146 BindAddress: "10.10.12.11", 147 HealthzBindAddress: "0.0.0.0:12345", 148 MetricsBindAddress: "127.0.0.1:10249", 149 ClusterCIDR: "fd00:192:168::/64", 150 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 151 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 152 MasqueradeAll: true, 153 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 154 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 155 }, 156 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 157 MaxPerCore: ptr.To[int32](1), 158 Min: ptr.To[int32](1), 159 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 160 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 161 }, 162 Logging: logsapi.LoggingConfiguration{ 163 Format: "text", 164 }, 165 }, { 166 BindAddress: "10.10.12.11", 167 HealthzBindAddress: "0.0.0.0:12345", 168 MetricsBindAddress: "127.0.0.1:10249", 169 ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64", 170 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 171 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 172 MasqueradeAll: true, 173 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 174 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 175 }, 176 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 177 MaxPerCore: ptr.To[int32](1), 178 Min: ptr.To[int32](1), 179 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 180 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 181 }, 182 Logging: logsapi.LoggingConfiguration{ 183 Format: "text", 184 }, 185 }, { 186 BindAddress: "10.10.12.11", 187 HealthzBindAddress: "0.0.0.0:12345", 188 MetricsBindAddress: "127.0.0.1:10249", 189 ClusterCIDR: "192.168.59.0/24", 190 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 191 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 192 MasqueradeAll: true, 193 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 194 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 195 }, 196 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 197 MaxPerCore: ptr.To[int32](1), 198 Min: ptr.To[int32](1), 199 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 200 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 201 }, 202 DetectLocalMode: kubeproxyconfig.LocalModeInterfaceNamePrefix, 203 DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ 204 InterfaceNamePrefix: "vethabcde", 205 }, 206 Logging: logsapi.LoggingConfiguration{ 207 Format: "text", 208 }, 209 }, { 210 BindAddress: "10.10.12.11", 211 HealthzBindAddress: "0.0.0.0:12345", 212 MetricsBindAddress: "127.0.0.1:10249", 213 ClusterCIDR: "192.168.59.0/24", 214 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 215 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 216 MasqueradeAll: true, 217 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 218 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 219 }, 220 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 221 MaxPerCore: ptr.To[int32](1), 222 Min: ptr.To[int32](1), 223 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 224 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 225 }, 226 DetectLocalMode: kubeproxyconfig.LocalModeBridgeInterface, 227 DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ 228 BridgeInterface: "avz", 229 }, 230 Logging: logsapi.LoggingConfiguration{ 231 Format: "text", 232 }, 233 }} 234 235 for _, successCase := range successCases { 236 if errs := Validate(&successCase); len(errs) != 0 { 237 t.Errorf("expected success: %v", errs) 238 } 239 } 240 241 newPath := field.NewPath("KubeProxyConfiguration") 242 testCases := map[string]struct { 243 config kubeproxyconfig.KubeProxyConfiguration 244 expectedErrs field.ErrorList 245 }{ 246 "invalid BindAddress": { 247 config: kubeproxyconfig.KubeProxyConfiguration{ 248 BindAddress: "10.10.12.11:2000", 249 HealthzBindAddress: "0.0.0.0:10256", 250 MetricsBindAddress: "127.0.0.1:10249", 251 ClusterCIDR: "192.168.59.0/24", 252 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 253 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 254 MasqueradeAll: true, 255 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 256 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 257 }, 258 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 259 MaxPerCore: ptr.To[int32](1), 260 Min: ptr.To[int32](1), 261 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 262 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 263 }, 264 Logging: logsapi.LoggingConfiguration{ 265 Format: "text", 266 }, 267 }, 268 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("BindAddress"), "10.10.12.11:2000", "not a valid textual representation of an IP address")}, 269 }, 270 "invalid HealthzBindAddress": { 271 config: kubeproxyconfig.KubeProxyConfiguration{ 272 BindAddress: "10.10.12.11", 273 HealthzBindAddress: "0.0.0.0", 274 MetricsBindAddress: "127.0.0.1:10249", 275 ClusterCIDR: "192.168.59.0/24", 276 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 277 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 278 MasqueradeAll: true, 279 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 280 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 281 }, 282 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 283 MaxPerCore: ptr.To[int32](1), 284 Min: ptr.To[int32](1), 285 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 286 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 287 }, 288 Logging: logsapi.LoggingConfiguration{ 289 Format: "text", 290 }, 291 }, 292 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0.0.0.0", "must be IP:port")}, 293 }, 294 "invalid MetricsBindAddress": { 295 config: kubeproxyconfig.KubeProxyConfiguration{ 296 BindAddress: "10.10.12.11", 297 HealthzBindAddress: "0.0.0.0:12345", 298 MetricsBindAddress: "127.0.0.1", 299 ClusterCIDR: "192.168.59.0/24", 300 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 301 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 302 MasqueradeAll: true, 303 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 304 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 305 }, 306 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 307 MaxPerCore: ptr.To[int32](1), 308 Min: ptr.To[int32](1), 309 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 310 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 311 }, 312 Logging: logsapi.LoggingConfiguration{ 313 Format: "text", 314 }, 315 }, 316 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("MetricsBindAddress"), "127.0.0.1", "must be IP:port")}, 317 }, 318 "ClusterCIDR missing subset range": { 319 config: kubeproxyconfig.KubeProxyConfiguration{ 320 BindAddress: "10.10.12.11", 321 HealthzBindAddress: "0.0.0.0:12345", 322 MetricsBindAddress: "127.0.0.1:10249", 323 ClusterCIDR: "192.168.59.0", 324 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 325 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 326 MasqueradeAll: true, 327 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 328 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 329 }, 330 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 331 MaxPerCore: ptr.To[int32](1), 332 Min: ptr.To[int32](1), 333 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 334 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 335 }, 336 Logging: logsapi.LoggingConfiguration{ 337 Format: "text", 338 }, 339 }, 340 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)")}, 341 }, 342 "Invalid number of ClusterCIDRs": { 343 config: kubeproxyconfig.KubeProxyConfiguration{ 344 BindAddress: "10.10.12.11", 345 HealthzBindAddress: "0.0.0.0:12345", 346 MetricsBindAddress: "127.0.0.1:10249", 347 ClusterCIDR: "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16", 348 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 349 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 350 MasqueradeAll: true, 351 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 352 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 353 }, 354 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 355 MaxPerCore: ptr.To[int32](1), 356 Min: ptr.To[int32](1), 357 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 358 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 359 }, 360 Logging: logsapi.LoggingConfiguration{ 361 Format: "text", 362 }, 363 }, 364 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)")}, 365 }, 366 "ConfigSyncPeriod must be > 0": { 367 config: kubeproxyconfig.KubeProxyConfiguration{ 368 BindAddress: "10.10.12.11", 369 HealthzBindAddress: "0.0.0.0:12345", 370 MetricsBindAddress: "127.0.0.1:10249", 371 ClusterCIDR: "192.168.59.0/24", 372 ConfigSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, 373 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 374 MasqueradeAll: true, 375 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 376 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 377 }, 378 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 379 MaxPerCore: ptr.To[int32](1), 380 Min: ptr.To[int32](1), 381 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 382 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 383 }, 384 Logging: logsapi.LoggingConfiguration{ 385 Format: "text", 386 }, 387 }, 388 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ConfigSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than 0")}, 389 }, 390 "IPVS mode selected without providing required SyncPeriod": { 391 config: kubeproxyconfig.KubeProxyConfiguration{ 392 BindAddress: "192.168.59.103", 393 HealthzBindAddress: "0.0.0.0:10256", 394 MetricsBindAddress: "127.0.0.1:10249", 395 ClusterCIDR: "192.168.59.0/24", 396 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 397 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 398 MasqueradeAll: true, 399 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 400 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 401 }, 402 // not specifying valid period in IPVS mode. 403 Mode: kubeproxyconfig.ProxyModeIPVS, 404 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 405 MaxPerCore: ptr.To[int32](1), 406 Min: ptr.To[int32](1), 407 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 408 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 409 }, 410 Logging: logsapi.LoggingConfiguration{ 411 Format: "text", 412 }, 413 }, 414 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeProxyIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0")}, 415 }, 416 "interfacePrefix is empty": { 417 config: kubeproxyconfig.KubeProxyConfiguration{ 418 BindAddress: "10.10.12.11", 419 HealthzBindAddress: "0.0.0.0:12345", 420 MetricsBindAddress: "127.0.0.1:10249", 421 ClusterCIDR: "192.168.59.0/24", 422 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 423 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 424 MasqueradeAll: true, 425 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 426 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 427 }, 428 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 429 MaxPerCore: ptr.To[int32](1), 430 Min: ptr.To[int32](1), 431 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 432 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 433 }, 434 DetectLocalMode: kubeproxyconfig.LocalModeInterfaceNamePrefix, 435 DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ 436 InterfaceNamePrefix: "", 437 }, 438 Logging: logsapi.LoggingConfiguration{ 439 Format: "text", 440 }, 441 }, 442 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfacePrefix"), "", "must not be empty")}, 443 }, 444 "bridgeInterfaceName is empty": { 445 config: kubeproxyconfig.KubeProxyConfiguration{ 446 BindAddress: "10.10.12.11", 447 HealthzBindAddress: "0.0.0.0:12345", 448 MetricsBindAddress: "127.0.0.1:10249", 449 ClusterCIDR: "192.168.59.0/24", 450 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 451 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 452 MasqueradeAll: true, 453 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 454 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 455 }, 456 Conntrack: 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 }, 462 DetectLocalMode: kubeproxyconfig.LocalModeBridgeInterface, 463 DetectLocal: kubeproxyconfig.DetectLocalConfiguration{ 464 InterfaceNamePrefix: "eth0", // we won't care about prefix since mode is not prefix 465 }, 466 Logging: logsapi.LoggingConfiguration{ 467 Format: "text", 468 }, 469 }, 470 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfaceName"), "", "must not be empty")}, 471 }, 472 "invalid DetectLocalMode": { 473 config: kubeproxyconfig.KubeProxyConfiguration{ 474 BindAddress: "10.10.12.11", 475 HealthzBindAddress: "0.0.0.0:12345", 476 MetricsBindAddress: "127.0.0.1:10249", 477 ClusterCIDR: "192.168.59.0/24", 478 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 479 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 480 MasqueradeAll: true, 481 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 482 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 483 }, 484 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 485 MaxPerCore: ptr.To[int32](1), 486 Min: ptr.To[int32](1), 487 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 488 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 489 }, 490 DetectLocalMode: "Guess", 491 Logging: logsapi.LoggingConfiguration{ 492 Format: "text", 493 }, 494 }, 495 expectedErrs: field.ErrorList{field.NotSupported(newPath.Child("DetectLocalMode"), "Guess", []string{"ClusterCIDR", "NodeCIDR", "BridgeInterface", "InterfaceNamePrefix", ""})}, 496 }, 497 "invalid logging format": { 498 config: kubeproxyconfig.KubeProxyConfiguration{ 499 BindAddress: "10.10.12.11", 500 HealthzBindAddress: "0.0.0.0:12345", 501 MetricsBindAddress: "127.0.0.1:10249", 502 ClusterCIDR: "192.168.59.0/24", 503 ConfigSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 504 IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 505 MasqueradeAll: true, 506 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 507 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 508 }, 509 Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{ 510 MaxPerCore: ptr.To[int32](1), 511 Min: ptr.To[int32](1), 512 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 513 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 514 }, 515 Logging: logsapi.LoggingConfiguration{ 516 Format: "unsupported format", 517 }, 518 }, 519 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("logging.format"), "unsupported format", "Unsupported log format")}, 520 }, 521 } 522 523 for name, testCase := range testCases { 524 if runtime.GOOS == "windows" && testCase.config.Mode == kubeproxyconfig.ProxyModeIPVS { 525 // IPVS is not supported on Windows. 526 t.Log("Skipping test on Windows: ", name) 527 continue 528 } 529 t.Run(name, func(t *testing.T) { 530 errs := Validate(&testCase.config) 531 if len(testCase.expectedErrs) != len(errs) { 532 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 533 } 534 for i, err := range errs { 535 if err.Error() != testCase.expectedErrs[i].Error() { 536 t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 537 } 538 } 539 }) 540 } 541 } 542 543 func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) { 544 newPath := field.NewPath("KubeProxyConfiguration") 545 546 testCases := map[string]struct { 547 config kubeproxyconfig.KubeProxyIPTablesConfiguration 548 expectedErrs field.ErrorList 549 }{ 550 "valid iptables config": { 551 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 552 MasqueradeAll: true, 553 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 554 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 555 }, 556 expectedErrs: field.ErrorList{}, 557 }, 558 "valid custom MasqueradeBit": { 559 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 560 MasqueradeBit: ptr.To[int32](5), 561 MasqueradeAll: true, 562 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 563 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 564 }, 565 expectedErrs: field.ErrorList{}, 566 }, 567 "SyncPeriod must be > 0": { 568 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 569 MasqueradeAll: true, 570 SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, 571 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 572 }, 573 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"), 574 field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")}, 575 }, 576 "MinSyncPeriod must be > 0": { 577 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 578 MasqueradeBit: ptr.To[int32](5), 579 MasqueradeAll: true, 580 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 581 MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, 582 }, 583 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")}, 584 }, 585 "MasqueradeBit cannot be < 0": { 586 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 587 MasqueradeBit: ptr.To[int32](-10), 588 MasqueradeAll: true, 589 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 590 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 591 }, 592 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MasqueradeBit"), -10, "must be within the range [0, 31]")}, 593 }, 594 "SyncPeriod must be >= MinSyncPeriod": { 595 config: kubeproxyconfig.KubeProxyIPTablesConfiguration{ 596 MasqueradeBit: ptr.To[int32](5), 597 MasqueradeAll: true, 598 SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 599 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 600 }, 601 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")}, 602 }, 603 } 604 605 for _, testCase := range testCases { 606 errs := validateKubeProxyIPTablesConfiguration(testCase.config, newPath.Child("KubeIPTablesConfiguration")) 607 if len(testCase.expectedErrs) != len(errs) { 608 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 609 } 610 for i, err := range errs { 611 if err.Error() != testCase.expectedErrs[i].Error() { 612 t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 613 } 614 } 615 } 616 } 617 618 func TestValidateKubeProxyIPVSConfiguration(t *testing.T) { 619 newPath := field.NewPath("KubeProxyConfiguration") 620 testCases := map[string]struct { 621 config kubeproxyconfig.KubeProxyIPVSConfiguration 622 expectedErrs field.ErrorList 623 }{ 624 "SyncPeriod is not greater than 0": { 625 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 626 SyncPeriod: metav1.Duration{Duration: -5 * time.Second}, 627 MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second}, 628 }, 629 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"), 630 field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")}, 631 }, 632 "SyncPeriod cannot be 0": { 633 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 634 SyncPeriod: metav1.Duration{Duration: 0 * time.Second}, 635 MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 636 }, 637 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0"), 638 field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 10 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")}, 639 }, 640 "MinSyncPeriod cannot be less than 0": { 641 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 642 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 643 MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second}, 644 }, 645 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")}, 646 }, 647 "SyncPeriod must be greater than MinSyncPeriod": { 648 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 649 SyncPeriod: metav1.Duration{Duration: 1 * time.Second}, 650 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 651 }, 652 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")}, 653 }, 654 "SyncPeriod == MinSyncPeriod": { 655 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 656 SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 657 MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 658 }, 659 expectedErrs: field.ErrorList{}, 660 }, 661 "SyncPeriod should be > MinSyncPeriod": { 662 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 663 SyncPeriod: metav1.Duration{Duration: 10 * time.Second}, 664 MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 665 }, 666 expectedErrs: field.ErrorList{}, 667 }, 668 "MinSyncPeriod can be 0": { 669 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 670 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 671 MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second}, 672 }, 673 expectedErrs: field.ErrorList{}, 674 }, 675 "IPVS Timeout can be 0": { 676 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 677 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 678 TCPTimeout: metav1.Duration{Duration: 0 * time.Second}, 679 TCPFinTimeout: metav1.Duration{Duration: 0 * time.Second}, 680 UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, 681 }, 682 expectedErrs: field.ErrorList{}, 683 }, 684 "IPVS Timeout > 0": { 685 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 686 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 687 TCPTimeout: metav1.Duration{Duration: 1 * time.Second}, 688 TCPFinTimeout: metav1.Duration{Duration: 2 * time.Second}, 689 UDPTimeout: metav1.Duration{Duration: 3 * time.Second}, 690 }, 691 expectedErrs: field.ErrorList{}, 692 }, 693 "TCP,TCPFin,UDP Timeouts < 0": { 694 config: kubeproxyconfig.KubeProxyIPVSConfiguration{ 695 SyncPeriod: metav1.Duration{Duration: 5 * time.Second}, 696 TCPTimeout: metav1.Duration{Duration: -1 * time.Second}, 697 UDPTimeout: metav1.Duration{Duration: -1 * time.Second}, 698 TCPFinTimeout: metav1.Duration{Duration: -1 * time.Second}, 699 }, 700 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"), 701 field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPFinTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"), 702 field.Invalid(newPath.Child("KubeIPVSConfiguration.UDPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")}, 703 }, 704 } 705 for _, testCase := range testCases { 706 errs := validateKubeProxyIPVSConfiguration(testCase.config, newPath.Child("KubeIPVSConfiguration")) 707 if len(testCase.expectedErrs) != len(errs) { 708 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 709 } 710 for i, err := range errs { 711 if err.Error() != testCase.expectedErrs[i].Error() { 712 t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 713 } 714 } 715 } 716 } 717 718 func TestValidateKubeProxyConntrackConfiguration(t *testing.T) { 719 newPath := field.NewPath("KubeProxyConfiguration") 720 testCases := map[string]struct { 721 config kubeproxyconfig.KubeProxyConntrackConfiguration 722 expectedErrs field.ErrorList 723 }{ 724 "valid 5 second timeouts": { 725 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 726 MaxPerCore: ptr.To[int32](1), 727 Min: ptr.To[int32](1), 728 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 729 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 730 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 731 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 732 }, 733 expectedErrs: field.ErrorList{}, 734 }, 735 "valid duration equal to 0 second timeout": { 736 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 737 MaxPerCore: ptr.To[int32](1), 738 Min: ptr.To[int32](1), 739 TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second}, 740 TCPCloseWaitTimeout: &metav1.Duration{Duration: 0 * time.Second}, 741 UDPTimeout: metav1.Duration{Duration: 0 * time.Second}, 742 UDPStreamTimeout: metav1.Duration{Duration: 0 * time.Second}, 743 }, 744 expectedErrs: field.ErrorList{}, 745 }, 746 "invalid MaxPerCore < 0": { 747 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 748 MaxPerCore: ptr.To[int32](-1), 749 Min: ptr.To[int32](1), 750 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 751 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 752 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 753 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 754 }, 755 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.MaxPerCore"), -1, "must be greater than or equal to 0")}, 756 }, 757 "invalid minimum < 0": { 758 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 759 MaxPerCore: ptr.To[int32](1), 760 Min: ptr.To[int32](-1), 761 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 762 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 763 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 764 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 765 }, 766 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.Min"), -1, "must be greater than or equal to 0")}, 767 }, 768 "invalid TCPEstablishedTimeout < 0": { 769 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 770 MaxPerCore: ptr.To[int32](1), 771 Min: ptr.To[int32](1), 772 TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second}, 773 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 774 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 775 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 776 }, 777 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPEstablishedTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 778 }, 779 "invalid TCPCloseWaitTimeout < 0": { 780 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 781 MaxPerCore: ptr.To[int32](1), 782 Min: ptr.To[int32](1), 783 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 784 TCPCloseWaitTimeout: &metav1.Duration{Duration: -5 * time.Second}, 785 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 786 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 787 }, 788 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPCloseWaitTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 789 }, 790 "invalid UDPTimeout < 0": { 791 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 792 MaxPerCore: ptr.To[int32](1), 793 Min: ptr.To[int32](1), 794 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 795 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 796 UDPTimeout: metav1.Duration{Duration: -5 * time.Second}, 797 UDPStreamTimeout: metav1.Duration{Duration: 5 * time.Second}, 798 }, 799 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 800 }, 801 "invalid UDPStreamTimeout < 0": { 802 config: kubeproxyconfig.KubeProxyConntrackConfiguration{ 803 MaxPerCore: ptr.To[int32](1), 804 Min: ptr.To[int32](1), 805 TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second}, 806 TCPCloseWaitTimeout: &metav1.Duration{Duration: 5 * time.Second}, 807 UDPTimeout: metav1.Duration{Duration: 5 * time.Second}, 808 UDPStreamTimeout: metav1.Duration{Duration: -5 * time.Second}, 809 }, 810 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPStreamTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")}, 811 }, 812 } 813 814 for _, testCase := range testCases { 815 errs := validateKubeProxyConntrackConfiguration(testCase.config, newPath.Child("KubeConntrackConfiguration")) 816 if len(testCase.expectedErrs) != len(errs) { 817 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 818 } 819 for i, err := range errs { 820 if err.Error() != testCase.expectedErrs[i].Error() { 821 t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 822 } 823 } 824 } 825 } 826 827 func TestValidateProxyMode(t *testing.T) { 828 newPath := field.NewPath("KubeProxyConfiguration") 829 successCases := []kubeproxyconfig.ProxyMode{""} 830 expectedNonExistentErrorMsg := "must be iptables, ipvs or blank (blank means the best-available proxy [currently iptables])" 831 832 if runtime.GOOS == "windows" { 833 successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace) 834 expectedNonExistentErrorMsg = "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])" 835 } else { 836 successCases = append(successCases, kubeproxyconfig.ProxyModeIPTables, kubeproxyconfig.ProxyModeIPVS) 837 } 838 839 for _, successCase := range successCases { 840 if errs := validateProxyMode(successCase, newPath.Child("ProxyMode")); len(errs) != 0 { 841 t.Errorf("expected success: %v", errs) 842 } 843 } 844 845 testCases := map[string]struct { 846 mode kubeproxyconfig.ProxyMode 847 expectedErrs field.ErrorList 848 }{ 849 "blank mode should default": { 850 mode: kubeproxyconfig.ProxyMode(""), 851 expectedErrs: field.ErrorList{}, 852 }, 853 "invalid mode non-existent": { 854 mode: kubeproxyconfig.ProxyMode("non-existing"), 855 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", expectedNonExistentErrorMsg)}, 856 }, 857 } 858 for _, testCase := range testCases { 859 errs := validateProxyMode(testCase.mode, newPath) 860 if len(testCase.expectedErrs) != len(errs) { 861 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 862 } 863 for i, err := range errs { 864 if err.Error() != testCase.expectedErrs[i].Error() { 865 t.Errorf("Expected error: %s, got %v", testCase.expectedErrs[i], err.Error()) 866 } 867 } 868 } 869 } 870 871 func TestValidateClientConnectionConfiguration(t *testing.T) { 872 newPath := field.NewPath("KubeProxyConfiguration") 873 874 testCases := map[string]struct { 875 ccc componentbaseconfig.ClientConnectionConfiguration 876 expectedErrs field.ErrorList 877 }{ 878 "successful 0 value": { 879 ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: 0}, 880 expectedErrs: field.ErrorList{}, 881 }, 882 "successful 5 value": { 883 ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: 5}, 884 expectedErrs: field.ErrorList{}, 885 }, 886 "burst < 0": { 887 ccc: componentbaseconfig.ClientConnectionConfiguration{Burst: -5}, 888 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("Burst"), -5, "must be greater than or equal to 0")}, 889 }, 890 } 891 892 for _, testCase := range testCases { 893 errs := validateClientConnectionConfiguration(testCase.ccc, newPath) 894 if len(testCase.expectedErrs) != len(errs) { 895 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 896 } 897 for i, err := range errs { 898 if err.Error() != testCase.expectedErrs[i].Error() { 899 t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 900 } 901 } 902 } 903 } 904 905 func TestValidateHostPort(t *testing.T) { 906 newPath := field.NewPath("KubeProxyConfiguration") 907 908 successCases := []string{ 909 "0.0.0.0:10256", 910 "127.0.0.1:10256", 911 "10.10.10.10:10256", 912 } 913 914 for _, successCase := range successCases { 915 if errs := validateHostPort(successCase, newPath.Child("HealthzBindAddress")); len(errs) != 0 { 916 t.Errorf("expected success: %v", errs) 917 } 918 } 919 920 errorCases := map[string]struct { 921 ip string 922 expectedErrs field.ErrorList 923 }{ 924 "missing port": { 925 ip: "10.10.10.10", 926 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "10.10.10.10", "must be IP:port")}, 927 }, 928 "digits outside of 1-255": { 929 ip: "123.456.789.10:12345", 930 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "123.456.789.10", "must be a valid IP")}, 931 }, 932 "invalid named-port": { 933 ip: "10.10.10.10:foo", 934 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "foo", "must be a valid port")}, 935 }, 936 "port cannot be 0": { 937 ip: "10.10.10.10:0", 938 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0", "must be a valid port")}, 939 }, 940 "port is greater than allowed range": { 941 ip: "10.10.10.10:65536", 942 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "65536", "must be a valid port")}, 943 }, 944 } 945 946 for _, errorCase := range errorCases { 947 errs := validateHostPort(errorCase.ip, newPath.Child("HealthzBindAddress")) 948 if len(errorCase.expectedErrs) != len(errs) { 949 t.Fatalf("Expected %d errors, got %d errors: %v", len(errorCase.expectedErrs), len(errs), errs) 950 } 951 for i, err := range errs { 952 if err.Error() != errorCase.expectedErrs[i].Error() { 953 t.Errorf("Expected error: %s, got %s", errorCase.expectedErrs[i], err.Error()) 954 } 955 } 956 } 957 } 958 959 func TestValidateKubeProxyNodePortAddress(t *testing.T) { 960 newPath := field.NewPath("KubeProxyConfiguration") 961 962 successCases := []struct { 963 addresses []string 964 }{ 965 {[]string{}}, 966 {[]string{"127.0.0.0/8"}}, 967 {[]string{"0.0.0.0/0"}}, 968 {[]string{"::/0"}}, 969 {[]string{"127.0.0.1/32", "1.2.3.0/24"}}, 970 {[]string{"127.0.0.0/8"}}, 971 {[]string{"127.0.0.1/32"}}, 972 {[]string{"::1/128"}}, 973 {[]string{"1.2.3.4/32"}}, 974 {[]string{"10.20.30.0/24"}}, 975 {[]string{"10.20.0.0/16", "100.200.0.0/16"}}, 976 {[]string{"10.0.0.0/8"}}, 977 {[]string{"2001:db8::/32"}}, 978 } 979 980 for _, successCase := range successCases { 981 if errs := validateKubeProxyNodePortAddress(successCase.addresses, newPath.Child("NodePortAddresses")); len(errs) != 0 { 982 t.Errorf("expected success: %v", errs) 983 } 984 } 985 986 testCases := map[string]struct { 987 addresses []string 988 expectedErrs field.ErrorList 989 }{ 990 "invalid foo address": { 991 addresses: []string{"foo"}, 992 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "foo", "must be a valid CIDR")}, 993 }, 994 "invalid octet address": { 995 addresses: []string{"10.0.0.0/0", "1.2.3"}, 996 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "1.2.3", "must be a valid CIDR")}, 997 }, 998 "address cannot be 0": { 999 addresses: []string{"127.0.0.1/32", "0", "1.2.3.0/24"}, 1000 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "0", "must be a valid CIDR")}, 1001 }, 1002 "address missing subnet range": { 1003 addresses: []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"}, 1004 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "10.20.30.40", "must be a valid CIDR")}, 1005 }, 1006 "missing ipv6 subnet ranges": { 1007 addresses: []string{"::0", "::1", "2001:db8::/32"}, 1008 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "::0", "must be a valid CIDR"), 1009 field.Invalid(newPath.Child("NodePortAddresses[1]"), "::1", "must be a valid CIDR")}, 1010 }, 1011 "invalid ipv6 ip format": { 1012 addresses: []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"}, 1013 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[2]"), "2001:db8:xyz/64", "must be a valid CIDR")}, 1014 }, 1015 } 1016 1017 for _, testCase := range testCases { 1018 errs := validateKubeProxyNodePortAddress(testCase.addresses, newPath.Child("NodePortAddresses")) 1019 if len(testCase.expectedErrs) != len(errs) { 1020 t.Errorf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 1021 } 1022 for i, err := range errs { 1023 if err.Error() != testCase.expectedErrs[i].Error() { 1024 t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 1025 } 1026 } 1027 } 1028 } 1029 1030 func TestValidateKubeProxyExcludeCIDRs(t *testing.T) { 1031 newPath := field.NewPath("KubeProxyConfiguration") 1032 1033 successCases := []struct { 1034 addresses []string 1035 }{ 1036 {[]string{}}, 1037 {[]string{"127.0.0.0/8"}}, 1038 {[]string{"0.0.0.0/0"}}, 1039 {[]string{"::/0"}}, 1040 {[]string{"127.0.0.1/32", "1.2.3.0/24"}}, 1041 {[]string{"127.0.0.0/8"}}, 1042 {[]string{"127.0.0.1/32"}}, 1043 {[]string{"::1/128"}}, 1044 {[]string{"1.2.3.4/32"}}, 1045 {[]string{"10.20.30.0/24"}}, 1046 {[]string{"10.20.0.0/16", "100.200.0.0/16"}}, 1047 {[]string{"10.0.0.0/8"}}, 1048 {[]string{"2001:db8::/32"}}, 1049 } 1050 1051 for _, successCase := range successCases { 1052 if errs := validateIPVSExcludeCIDRs(successCase.addresses, newPath.Child("ExcludeCIDRs")); len(errs) != 0 { 1053 t.Errorf("expected success: %v", errs) 1054 } 1055 } 1056 1057 testCases := map[string]struct { 1058 addresses []string 1059 expectedErrs field.ErrorList 1060 }{ 1061 "invalid foo address": { 1062 addresses: []string{"foo"}, 1063 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "foo", "must be a valid CIDR")}, 1064 }, 1065 "invalid octet address": { 1066 addresses: []string{"10.0.0.0/0", "1.2.3"}, 1067 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "1.2.3", "must be a valid CIDR")}, 1068 }, 1069 "address cannot be 0": { 1070 addresses: []string{"127.0.0.1/32", "0", "1.2.3.0/24"}, 1071 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "0", "must be a valid CIDR")}, 1072 }, 1073 "address missing subnet range": { 1074 addresses: []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"}, 1075 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "10.20.30.40", "must be a valid CIDR")}, 1076 }, 1077 "missing ipv6 subnet ranges": { 1078 addresses: []string{"::0", "::1", "2001:db8::/32"}, 1079 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "::0", "must be a valid CIDR"), 1080 field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "::1", "must be a valid CIDR")}, 1081 }, 1082 "invalid ipv6 ip format": { 1083 addresses: []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"}, 1084 expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[2]"), "2001:db8:xyz/64", "must be a valid CIDR")}, 1085 }, 1086 } 1087 1088 for _, testCase := range testCases { 1089 errs := validateIPVSExcludeCIDRs(testCase.addresses, newPath.Child("ExcludeCIDRS")) 1090 if len(testCase.expectedErrs) != len(errs) { 1091 t.Errorf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs) 1092 } 1093 for i, err := range errs { 1094 if err.Error() != testCase.expectedErrs[i].Error() { 1095 t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error()) 1096 } 1097 } 1098 } 1099 }