...

Source file src/k8s.io/kubernetes/pkg/kubelet/apis/config/validation/validation_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/apis/config/validation

     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  }
   627  

View as plain text