1
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 }
627
View as plain text