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