k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/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_test 18 19 import ( 20 "strings" 21 "testing" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 logsapi "k8s.io/component-base/logs/api/v1" 28 tracingapi "k8s.io/component-base/tracing/api/v1" 29 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" 30 "k8s.io/kubernetes/pkg/kubelet/apis/config/validation" 31 kubetypes "k8s.io/kubernetes/pkg/kubelet/types" 32 utilpointer "k8s.io/utils/pointer" 33 ) 34 35 var ( 36 successConfig = kubeletconfig.KubeletConfiguration{ 37 CgroupsPerQOS: cgroupsPerQOS, 38 EnforceNodeAllocatable: enforceNodeAllocatable, 39 SystemReservedCgroup: "/system.slice", 40 KubeReservedCgroup: "/kubelet.service", 41 PodLogsDir: "/logs", 42 SystemCgroups: "", 43 CgroupRoot: "", 44 EventBurst: 10, 45 EventRecordQPS: 5, 46 HealthzPort: 10248, 47 ImageGCHighThresholdPercent: 85, 48 ImageGCLowThresholdPercent: 80, 49 IPTablesDropBit: 15, 50 IPTablesMasqueradeBit: 14, 51 KubeAPIBurst: 10, 52 KubeAPIQPS: 5, 53 MaxOpenFiles: 1000000, 54 MaxPods: 110, 55 OOMScoreAdj: -999, 56 PodsPerCore: 100, 57 Port: 65535, 58 ReadOnlyPort: 0, 59 RegistryBurst: 10, 60 RegistryPullQPS: 5, 61 MaxParallelImagePulls: nil, 62 HairpinMode: kubeletconfig.PromiscuousBridge, 63 NodeLeaseDurationSeconds: 1, 64 CPUCFSQuotaPeriod: metav1.Duration{Duration: 25 * time.Millisecond}, 65 TopologyManagerScope: kubeletconfig.PodTopologyManagerScope, 66 TopologyManagerPolicy: kubeletconfig.SingleNumaNodeTopologyManagerPolicy, 67 ShutdownGracePeriod: metav1.Duration{Duration: 30 * time.Second}, 68 ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 10 * time.Second}, 69 MemoryThrottlingFactor: utilpointer.Float64(0.9), 70 FeatureGates: map[string]bool{ 71 "CustomCPUCFSQuotaPeriod": true, 72 "GracefulNodeShutdown": true, 73 "MemoryQoS": true, 74 }, 75 Logging: logsapi.LoggingConfiguration{ 76 Format: "text", 77 }, 78 ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock", 79 ContainerLogMaxWorkers: 1, 80 ContainerLogMonitorInterval: metav1.Duration{Duration: 10 * time.Second}, 81 } 82 ) 83 84 func TestValidateKubeletConfiguration(t *testing.T) { 85 featureGate := utilfeature.DefaultFeatureGate.DeepCopy() 86 logsapi.AddFeatureGates(featureGate) 87 88 cases := []struct { 89 name string 90 configure func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration 91 errMsg string 92 }{{ 93 name: "Success", 94 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 95 return conf 96 }, 97 }, { 98 name: "invalid NodeLeaseDurationSeconds", 99 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 100 conf.NodeLeaseDurationSeconds = 0 101 return conf 102 }, 103 errMsg: "invalid configuration: nodeLeaseDurationSeconds must be greater than 0", 104 }, { 105 name: "specify EnforceNodeAllocatable without enabling CgroupsPerQOS", 106 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 107 conf.CgroupsPerQOS = false 108 conf.EnforceNodeAllocatable = []string{"pods"} 109 return conf 110 }, 111 errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) is not supported unless cgroupsPerQOS (--cgroups-per-qos) is set to true", 112 }, { 113 name: "specify SystemCgroups without CgroupRoot", 114 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 115 conf.SystemCgroups = "/" 116 conf.CgroupRoot = "" 117 return conf 118 }, 119 errMsg: "invalid configuration: systemCgroups (--system-cgroups) was specified and cgroupRoot (--cgroup-root) was not specified", 120 }, { 121 name: "invalid EventBurst", 122 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 123 conf.EventBurst = -1 124 return conf 125 }, 126 errMsg: "invalid configuration: eventBurst (--event-burst) -1 must not be a negative number", 127 }, { 128 name: "invalid EventRecordQPS", 129 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 130 conf.EventRecordQPS = -1 131 return conf 132 }, 133 errMsg: "invalid configuration: eventRecordQPS (--event-qps) -1 must not be a negative number", 134 }, { 135 name: "invalid HealthzPort", 136 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 137 conf.HealthzPort = 65536 138 return conf 139 }, 140 errMsg: "invalid configuration: healthzPort (--healthz-port) 65536 must be between 1 and 65535, inclusive", 141 }, { 142 name: "specify CPUCFSQuotaPeriod without enabling CPUCFSQuotaPeriod", 143 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 144 conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": false} 145 conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 200 * time.Millisecond} 146 return conf 147 }, 148 errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {200ms} requires feature gate CustomCPUCFSQuotaPeriod", 149 }, { 150 name: "invalid CPUCFSQuotaPeriod", 151 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 152 conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": true} 153 conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 2 * time.Second} 154 return conf 155 }, 156 errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {2s} must be between 1ms and 1sec, inclusive", 157 }, { 158 name: "invalid ImageGCHighThresholdPercent", 159 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 160 conf.ImageGCHighThresholdPercent = 101 161 return conf 162 }, 163 errMsg: "invalid configuration: imageGCHighThresholdPercent (--image-gc-high-threshold) 101 must be between 0 and 100, inclusive", 164 }, { 165 name: "invalid ImageGCLowThresholdPercent", 166 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 167 conf.ImageGCLowThresholdPercent = -1 168 return conf 169 }, 170 errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) -1 must be between 0 and 100, inclusive", 171 }, { 172 name: "ImageGCLowThresholdPercent is equal to ImageGCHighThresholdPercent", 173 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 174 conf.ImageGCHighThresholdPercent = 0 175 conf.ImageGCLowThresholdPercent = 0 176 return conf 177 }, 178 errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 0 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", 179 }, { 180 name: "ImageGCLowThresholdPercent is greater than ImageGCHighThresholdPercent", 181 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 182 conf.ImageGCHighThresholdPercent = 0 183 conf.ImageGCLowThresholdPercent = 1 184 return conf 185 }, 186 errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 1 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", 187 }, { 188 name: "invalid IPTablesDropBit", 189 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 190 conf.IPTablesDropBit = 32 191 return conf 192 }, 193 errMsg: "invalid configuration: iptablesDropBit (--iptables-drop-bit) 32 must be between 0 and 31, inclusive", 194 }, { 195 name: "invalid IPTablesMasqueradeBit", 196 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 197 conf.IPTablesMasqueradeBit = 32 198 return conf 199 }, 200 errMsg: "invalid configuration: iptablesMasqueradeBit (--iptables-masquerade-bit) 32 must be between 0 and 31, inclusive", 201 }, { 202 name: "invalid KubeAPIBurst", 203 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 204 conf.KubeAPIBurst = -1 205 return conf 206 }, 207 errMsg: "invalid configuration: kubeAPIBurst (--kube-api-burst) -1 must not be a negative number", 208 }, { 209 name: "invalid KubeAPIQPS", 210 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 211 conf.KubeAPIQPS = -1 212 return conf 213 }, 214 errMsg: "invalid configuration: kubeAPIQPS (--kube-api-qps) -1 must not be a negative number", 215 }, { 216 name: "invalid NodeStatusMaxImages", 217 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 218 conf.NodeStatusMaxImages = -2 219 return conf 220 }, 221 errMsg: "invalid configuration: nodeStatusMaxImages (--node-status-max-images) -2 must be -1 or greater", 222 }, { 223 name: "invalid MaxOpenFiles", 224 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 225 conf.MaxOpenFiles = -1 226 return conf 227 }, 228 errMsg: "invalid configuration: maxOpenFiles (--max-open-files) -1 must not be a negative number", 229 }, { 230 name: "invalid MaxPods", 231 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 232 conf.MaxPods = -1 233 return conf 234 }, 235 errMsg: "invalid configuration: maxPods (--max-pods) -1 must not be a negative number", 236 }, { 237 name: "invalid OOMScoreAdj", 238 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 239 conf.OOMScoreAdj = 1001 240 return conf 241 }, 242 errMsg: "invalid configuration: oomScoreAdj (--oom-score-adj) 1001 must be between -1000 and 1000, inclusive", 243 }, { 244 name: "invalid PodsPerCore", 245 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 246 conf.PodsPerCore = -1 247 return conf 248 }, 249 errMsg: "invalid configuration: podsPerCore (--pods-per-core) -1 must not be a negative number", 250 }, { 251 name: "invalid Port", 252 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 253 conf.Port = 65536 254 return conf 255 }, 256 errMsg: "invalid configuration: port (--port) 65536 must be between 1 and 65535, inclusive", 257 }, { 258 name: "invalid ReadOnlyPort", 259 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 260 conf.ReadOnlyPort = 65536 261 return conf 262 }, 263 errMsg: "invalid configuration: readOnlyPort (--read-only-port) 65536 must be between 0 and 65535, inclusive", 264 }, { 265 name: "invalid RegistryBurst", 266 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 267 conf.RegistryBurst = -1 268 return conf 269 }, 270 errMsg: "invalid configuration: registryBurst (--registry-burst) -1 must not be a negative number", 271 }, { 272 name: "invalid RegistryPullQPS", 273 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 274 conf.RegistryPullQPS = -1 275 return conf 276 }, 277 errMsg: "invalid configuration: registryPullQPS (--registry-qps) -1 must not be a negative number", 278 }, { 279 name: "invalid MaxParallelImagePulls", 280 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 281 conf.MaxParallelImagePulls = utilpointer.Int32(0) 282 return conf 283 }, 284 errMsg: "invalid configuration: maxParallelImagePulls 0 must be a positive number", 285 }, { 286 name: "invalid MaxParallelImagePulls and SerializeImagePulls combination", 287 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 288 conf.MaxParallelImagePulls = utilpointer.Int32(3) 289 conf.SerializeImagePulls = true 290 return conf 291 }, 292 errMsg: "invalid configuration: maxParallelImagePulls cannot be larger than 1 unless SerializeImagePulls (--serialize-image-pulls) is set to false", 293 }, { 294 name: "valid MaxParallelImagePulls and SerializeImagePulls combination", 295 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 296 conf.MaxParallelImagePulls = utilpointer.Int32(1) 297 conf.SerializeImagePulls = true 298 return conf 299 }, 300 }, { 301 name: "specify ServerTLSBootstrap without enabling RotateKubeletServerCertificate", 302 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 303 conf.FeatureGates = map[string]bool{"RotateKubeletServerCertificate": false} 304 conf.ServerTLSBootstrap = true 305 return conf 306 }, 307 errMsg: "invalid configuration: serverTLSBootstrap true requires feature gate RotateKubeletServerCertificate", 308 }, { 309 name: "invalid TopologyManagerPolicy", 310 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 311 conf.TopologyManagerPolicy = "invalid-policy" 312 return conf 313 }, 314 errMsg: "invalid configuration: topologyManagerPolicy (--topology-manager-policy) \"invalid-policy\" must be one of: [\"none\" \"best-effort\" \"restricted\" \"single-numa-node\"]", 315 }, { 316 name: "invalid TopologyManagerScope", 317 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 318 conf.TopologyManagerScope = "invalid-scope" 319 return conf 320 }, 321 errMsg: "invalid configuration: topologyManagerScope (--topology-manager-scope) \"invalid-scope\" must be one of: \"container\", or \"pod\"", 322 }, { 323 name: "ShutdownGracePeriodCriticalPods is greater than ShutdownGracePeriod", 324 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 325 conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} 326 conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 2 * time.Second} 327 conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} 328 return conf 329 }, 330 errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {2s} must be <= shutdownGracePeriod {1s}", 331 }, { 332 name: "ShutdownGracePeriod is less than 1 sec", 333 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 334 conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} 335 conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Millisecond} 336 return conf 337 }, 338 errMsg: "invalid configuration: shutdownGracePeriod {1ms} must be either zero or otherwise >= 1 sec", 339 }, { 340 name: "ShutdownGracePeriodCriticalPods is less than 1 sec", 341 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 342 conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} 343 conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Millisecond} 344 return conf 345 }, 346 errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {1ms} must be either zero or otherwise >= 1 sec", 347 }, { 348 name: "specify ShutdownGracePeriod without enabling GracefulNodeShutdown", 349 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 350 conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} 351 conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} 352 return conf 353 }, 354 errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", 355 }, { 356 name: "specify ShutdownGracePeriodCriticalPods without enabling GracefulNodeShutdown", 357 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 358 conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} 359 conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Second} 360 return conf 361 }, 362 errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", 363 }, { 364 name: "invalid MemorySwap.SwapBehavior", 365 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 366 conf.FeatureGates = map[string]bool{"NodeSwap": true} 367 conf.MemorySwap.SwapBehavior = "invalid-behavior" 368 return conf 369 }, 370 errMsg: "invalid configuration: memorySwap.swapBehavior \"invalid-behavior\" must be one of: \"\", \"LimitedSwap\" or \"NoSwap\"", 371 }, { 372 name: "specify MemorySwap.SwapBehavior without enabling NodeSwap", 373 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 374 conf.FeatureGates = map[string]bool{"NodeSwap": false} 375 conf.MemorySwap.SwapBehavior = kubetypes.LimitedSwap 376 return conf 377 }, 378 errMsg: "invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled", 379 }, { 380 name: "specify SystemReservedEnforcementKey without specifying SystemReservedCgroup", 381 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 382 conf.EnforceNodeAllocatable = []string{kubetypes.SystemReservedEnforcementKey} 383 conf.SystemReservedCgroup = "" 384 return conf 385 }, 386 errMsg: "invalid configuration: systemReservedCgroup (--system-reserved-cgroup) must be specified when \"system-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", 387 }, { 388 name: "specify KubeReservedEnforcementKey without specifying KubeReservedCgroup", 389 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 390 conf.EnforceNodeAllocatable = []string{kubetypes.KubeReservedEnforcementKey} 391 conf.KubeReservedCgroup = "" 392 return conf 393 }, 394 errMsg: "invalid configuration: kubeReservedCgroup (--kube-reserved-cgroup) must be specified when \"kube-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", 395 }, { 396 name: "specify NodeAllocatableNoneKey with additional enforcements", 397 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 398 conf.EnforceNodeAllocatable = []string{kubetypes.NodeAllocatableNoneKey, kubetypes.KubeReservedEnforcementKey} 399 return conf 400 }, 401 errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) may not contain additional enforcements when \"none\" is specified", 402 }, { 403 name: "invalid EnforceNodeAllocatable", 404 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 405 conf.EnforceNodeAllocatable = []string{"invalid-enforce-node-allocatable"} 406 return conf 407 }, 408 errMsg: "invalid configuration: option \"invalid-enforce-node-allocatable\" specified for enforceNodeAllocatable (--enforce-node-allocatable). Valid options are \"pods\", \"system-reserved\", \"kube-reserved\", or \"none\"", 409 }, { 410 name: "invalid HairpinMode", 411 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 412 conf.HairpinMode = "invalid-hair-pin-mode" 413 return conf 414 }, 415 errMsg: "invalid configuration: option \"invalid-hair-pin-mode\" specified for hairpinMode (--hairpin-mode). Valid options are \"none\", \"hairpin-veth\" or \"promiscuous-bridge\"", 416 }, { 417 name: "specify ReservedSystemCPUs with SystemReservedCgroup", 418 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 419 conf.ReservedSystemCPUs = "0-3" 420 conf.SystemReservedCgroup = "/system.slice" 421 return conf 422 }, 423 errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", 424 }, { 425 name: "specify ReservedSystemCPUs with KubeReservedCgroup", 426 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 427 conf.ReservedSystemCPUs = "0-3" 428 conf.KubeReservedCgroup = "/system.slice" 429 return conf 430 }, 431 errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", 432 }, { 433 name: "invalid ReservedSystemCPUs", 434 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 435 conf.ReservedSystemCPUs = "invalid-reserved-system-cpus" 436 return conf 437 }, 438 errMsg: "invalid configuration: unable to parse reservedSystemCPUs (--reserved-cpus) invalid-reserved-system-cpus, error:", 439 }, { 440 name: "enable MemoryQoS without specifying MemoryThrottlingFactor", 441 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 442 conf.FeatureGates = map[string]bool{"MemoryQoS": true} 443 conf.MemoryThrottlingFactor = nil 444 return conf 445 }, 446 errMsg: "invalid configuration: memoryThrottlingFactor is required when MemoryQoS feature flag is enabled", 447 }, { 448 name: "invalid MemoryThrottlingFactor", 449 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 450 conf.MemoryThrottlingFactor = utilpointer.Float64(1.1) 451 return conf 452 }, 453 errMsg: "invalid configuration: memoryThrottlingFactor 1.1 must be greater than 0 and less than or equal to 1.0", 454 }, { 455 name: "invalid Taint.TimeAdded", 456 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 457 now := metav1.Now() 458 conf.RegisterWithTaints = []v1.Taint{{TimeAdded: &now}} 459 return conf 460 }, 461 errMsg: "invalid configuration: taint.TimeAdded is not nil", 462 }, { 463 name: "specify tracing with KubeletTracing disabled", 464 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 465 samplingRate := int32(99999) 466 conf.FeatureGates = map[string]bool{"KubeletTracing": false} 467 conf.Tracing = &tracingapi.TracingConfiguration{SamplingRatePerMillion: &samplingRate} 468 return conf 469 }, 470 errMsg: "invalid configuration: tracing should not be configured if KubeletTracing feature flag is disabled.", 471 }, { 472 name: "specify tracing invalid sampling rate", 473 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 474 samplingRate := int32(-1) 475 conf.FeatureGates = map[string]bool{"KubeletTracing": true} 476 conf.Tracing = &tracingapi.TracingConfiguration{SamplingRatePerMillion: &samplingRate} 477 return conf 478 }, 479 errMsg: "tracing.samplingRatePerMillion: Invalid value: -1: sampling rate must be positive", 480 }, { 481 name: "specify tracing invalid endpoint", 482 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 483 ep := "dn%2s://localhost:4317" 484 conf.FeatureGates = map[string]bool{"KubeletTracing": true} 485 conf.Tracing = &tracingapi.TracingConfiguration{Endpoint: &ep} 486 return conf 487 }, 488 errMsg: "tracing.endpoint: Invalid value: \"dn%2s://localhost:4317\": parse \"dn%2s://localhost:4317\": first path segment in URL cannot contain colon", 489 }, { 490 name: "invalid GracefulNodeShutdownBasedOnPodPriority", 491 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 492 conf.FeatureGates = map[string]bool{"GracefulNodeShutdownBasedOnPodPriority": true} 493 conf.ShutdownGracePeriodByPodPriority = []kubeletconfig.ShutdownGracePeriodByPodPriority{{ 494 Priority: 0, 495 ShutdownGracePeriodSeconds: 0, 496 }} 497 return conf 498 }, 499 errMsg: "invalid configuration: Cannot specify both shutdownGracePeriodByPodPriority and shutdownGracePeriod at the same time", 500 }, { 501 name: "Specifying shutdownGracePeriodByPodPriority without enable GracefulNodeShutdownBasedOnPodPriority", 502 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 503 conf.FeatureGates = map[string]bool{"GracefulNodeShutdownBasedOnPodPriority": false} 504 conf.ShutdownGracePeriodByPodPriority = []kubeletconfig.ShutdownGracePeriodByPodPriority{{ 505 Priority: 0, 506 ShutdownGracePeriodSeconds: 0, 507 }} 508 return conf 509 }, 510 errMsg: "invalid configuration: Specifying shutdownGracePeriodByPodPriority requires feature gate GracefulNodeShutdownBasedOnPodPriority", 511 }, { 512 name: "enableSystemLogQuery is enabled without NodeLogQuery feature gate", 513 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 514 conf.EnableSystemLogQuery = true 515 return conf 516 }, 517 errMsg: "invalid configuration: NodeLogQuery feature gate is required for enableSystemLogHandler", 518 }, { 519 name: "enableSystemLogQuery is enabled without enableSystemLogHandler", 520 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 521 conf.FeatureGates = map[string]bool{"NodeLogQuery": true} 522 conf.EnableSystemLogHandler = false 523 conf.EnableSystemLogQuery = true 524 return conf 525 }, 526 errMsg: "invalid configuration: enableSystemLogHandler is required for enableSystemLogQuery", 527 }, { 528 name: "imageMaximumGCAge should not be specified without feature gate", 529 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 530 conf.FeatureGates = map[string]bool{"ImageMaximumGCAge": false} 531 conf.ImageMaximumGCAge = metav1.Duration{Duration: 1} 532 return conf 533 }, 534 errMsg: "invalid configuration: ImageMaximumGCAge feature gate is required for Kubelet configuration option imageMaximumGCAge", 535 }, { 536 name: "imageMaximumGCAge should not be negative", 537 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 538 conf.FeatureGates = map[string]bool{"ImageMaximumGCAge": true} 539 conf.ImageMaximumGCAge = metav1.Duration{Duration: -1} 540 return conf 541 }, 542 errMsg: "invalid configuration: imageMaximumGCAge -1ns must not be negative", 543 }, { 544 name: "imageMaximumGCAge should not be less than imageMinimumGCAge", 545 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 546 conf.FeatureGates = map[string]bool{"ImageMaximumGCAge": true} 547 conf.ImageMaximumGCAge = metav1.Duration{Duration: 1} 548 conf.ImageMinimumGCAge = metav1.Duration{Duration: 2} 549 return conf 550 }, 551 errMsg: "invalid configuration: imageMaximumGCAge 1ns must be greater than imageMinimumGCAge 2ns", 552 }, { 553 name: "containerLogMaxWorkers must be greater than or equal to 1", 554 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 555 conf.ContainerLogMaxWorkers = 0 556 return conf 557 }, 558 errMsg: "invalid configuration: containerLogMaxWorkers must be greater than or equal to 1", 559 }, { 560 name: "containerLogMonitorInterval must be a positive time duration", 561 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 562 conf.ContainerLogMonitorInterval = metav1.Duration{Duration: -1 * time.Second} 563 return conf 564 }, 565 errMsg: "invalid configuration: containerLogMonitorInterval must be a positive time duration greater than or equal to 3s", 566 }, { 567 name: "containerLogMonitorInterval must be at least 3s or higher", 568 configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 569 conf.ContainerLogMonitorInterval = metav1.Duration{Duration: 2 * time.Second} 570 return conf 571 }, 572 errMsg: "invalid configuration: containerLogMonitorInterval must be a positive time duration greater than or equal to 3s", 573 }, { 574 name: "pod logs path must be not empty", 575 configure: func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 576 config.PodLogsDir = "" 577 return config 578 }, 579 errMsg: "invalid configuration: podLogsDir was not specified", 580 }, { 581 name: "pod logs path must be absolute", 582 configure: func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 583 config.PodLogsDir = "./test" 584 return config 585 }, 586 errMsg: `invalid configuration: pod logs path "./test" must be absolute path`, 587 }, { 588 name: "pod logs path must be normalized", 589 configure: func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 590 config.PodLogsDir = "/path/../" 591 return config 592 }, 593 errMsg: `invalid configuration: pod logs path "/path/../" must be normalized`, 594 }, { 595 name: "pod logs path is ascii only", 596 configure: func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { 597 config.PodLogsDir = "/🧪" 598 return config 599 }, 600 errMsg: `invalid configuration: pod logs path "/🧪" mut contains ASCII characters only`, 601 }, 602 } 603 604 for _, tc := range cases { 605 t.Run(tc.name, func(t *testing.T) { 606 errs := validation.ValidateKubeletConfiguration(tc.configure(successConfig.DeepCopy()), featureGate) 607 608 if len(tc.errMsg) == 0 { 609 if errs != nil { 610 t.Errorf("unexpected error: %s", errs) 611 } 612 613 return 614 } 615 616 if errs == nil { 617 t.Errorf("expected error: %s", tc.errMsg) 618 return 619 } 620 621 if got := errs.Error(); !strings.Contains(got, tc.errMsg) { 622 t.Errorf("unexpected error: %s expected to contain %s", got, tc.errMsg) 623 } 624 }) 625 } 626 }