From 2acdaeb36118d0cb3867b551fb3a05c6507ebd29 Mon Sep 17 00:00:00 2001 From: shuheiktgw Date: Thu, 18 Nov 2021 08:03:32 +0900 Subject: [PATCH] Refactor Kubelet config validation tests --- .../validation_reserved_memory_test.go | 6 +- .../apis/config/validation/validation_test.go | 623 ++++++++++++------ 2 files changed, 435 insertions(+), 194 deletions(-) diff --git a/pkg/kubelet/apis/config/validation/validation_reserved_memory_test.go b/pkg/kubelet/apis/config/validation/validation_reserved_memory_test.go index 1910ffaa1eb..d1850375c23 100644 --- a/pkg/kubelet/apis/config/validation/validation_reserved_memory_test.go +++ b/pkg/kubelet/apis/config/validation/validation_reserved_memory_test.go @@ -68,7 +68,7 @@ func TestValidateReservedMemoryConfiguration(t *testing.T) { }, }, }, - expectedError: fmt.Errorf("the reserved memory has a duplicate value for NUMA node %d and resource %q", 0, v1.ResourceMemory), + expectedError: fmt.Errorf("invalid configuration: the reserved memory has a duplicate value for NUMA node %d and resource %q", 0, v1.ResourceMemory), }, { description: "The reserved memory has unsupported limit type", @@ -82,7 +82,7 @@ func TestValidateReservedMemoryConfiguration(t *testing.T) { }, }, }, - expectedError: fmt.Errorf("the limit type %q for NUMA node %d is not supported, only [memory hugepages-] is accepted", "blabla", 0), + expectedError: fmt.Errorf("invalid configuration: the limit type %q for NUMA node %d is not supported, only [memory hugepages-] is accepted", "blabla", 0), }, { description: "The reserved memory has limit type with zero value", @@ -96,7 +96,7 @@ func TestValidateReservedMemoryConfiguration(t *testing.T) { }, }, }, - expectedError: fmt.Errorf("reserved memory may not be zero for NUMA node %d and resource %q", 0, v1.ResourceMemory), + expectedError: fmt.Errorf("invalid configuration: reserved memory may not be zero for NUMA node %d and resource %q", 0, v1.ResourceMemory), }, } diff --git a/pkg/kubelet/apis/config/validation/validation_test.go b/pkg/kubelet/apis/config/validation/validation_test.go index 2128cbbe423..37e4d0b0c7b 100644 --- a/pkg/kubelet/apis/config/validation/validation_test.go +++ b/pkg/kubelet/apis/config/validation/validation_test.go @@ -14,22 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -package validation +package validation_test import ( + "strings" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilerrors "k8s.io/apimachinery/pkg/util/errors" componentbaseconfig "k8s.io/component-base/config" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" + "k8s.io/kubernetes/pkg/kubelet/apis/config/validation" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" utilpointer "k8s.io/utils/pointer" ) -func TestValidateKubeletConfiguration(t *testing.T) { - successCase1 := &kubeletconfig.KubeletConfiguration{ +var ( + successConfig = kubeletconfig.KubeletConfiguration{ CgroupsPerQOS: true, EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved"}, SystemReservedCgroup: "/system.slice", @@ -70,200 +71,440 @@ func TestValidateKubeletConfiguration(t *testing.T) { Format: "text", }, } - if allErrors := ValidateKubeletConfiguration(successCase1); allErrors != nil { - t.Errorf("expect no errors, got %v", allErrors) - } +) - successCase2 := &kubeletconfig.KubeletConfiguration{ - CgroupsPerQOS: true, - EnforceNodeAllocatable: []string{"pods"}, - SystemReservedCgroup: "", - KubeReservedCgroup: "", - SystemCgroups: "", - CgroupRoot: "", - EventBurst: 10, - EventRecordQPS: 5, - HealthzPort: 10248, - ImageGCHighThresholdPercent: 85, - ImageGCLowThresholdPercent: 80, - IPTablesDropBit: 15, - IPTablesMasqueradeBit: 14, - KubeAPIBurst: 10, - KubeAPIQPS: 5, - MaxOpenFiles: 1000000, - MaxPods: 110, - OOMScoreAdj: -999, - PodsPerCore: 100, - Port: 65535, - ReadOnlyPort: 0, - RegistryBurst: 10, - RegistryPullQPS: 5, - HairpinMode: kubeletconfig.PromiscuousBridge, - NodeLeaseDurationSeconds: 1, - CPUCFSQuotaPeriod: metav1.Duration{Duration: 50 * time.Millisecond}, - ReservedSystemCPUs: "0-3", - TopologyManagerScope: kubeletconfig.ContainerTopologyManagerScope, - TopologyManagerPolicy: kubeletconfig.NoneTopologyManagerPolicy, - ShutdownGracePeriod: metav1.Duration{Duration: 10 * time.Minute}, - ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 0}, - MemoryThrottlingFactor: utilpointer.Float64Ptr(0.9), - FeatureGates: map[string]bool{ - "CustomCPUCFSQuotaPeriod": true, - "MemoryQoS": true, - "GracefulNodeShutdownBasedOnPodPriority": true, - }, - Logging: componentbaseconfig.LoggingConfiguration{ - Format: "text", - }, - } - if allErrors := ValidateKubeletConfiguration(successCase2); allErrors != nil { - t.Errorf("expect no errors, got %v", allErrors) - } - - successCase3 := &kubeletconfig.KubeletConfiguration{ - CgroupsPerQOS: true, - EnforceNodeAllocatable: []string{"pods"}, - SystemReservedCgroup: "", - KubeReservedCgroup: "", - SystemCgroups: "", - CgroupRoot: "", - EventBurst: 10, - EventRecordQPS: 5, - HealthzPort: 10248, - ImageGCHighThresholdPercent: 85, - ImageGCLowThresholdPercent: 80, - IPTablesDropBit: 15, - IPTablesMasqueradeBit: 14, - KubeAPIBurst: 10, - KubeAPIQPS: 5, - MaxOpenFiles: 1000000, - MaxPods: 110, - OOMScoreAdj: -999, - PodsPerCore: 100, - Port: 65535, - ReadOnlyPort: 0, - RegistryBurst: 10, - RegistryPullQPS: 5, - HairpinMode: kubeletconfig.PromiscuousBridge, - NodeLeaseDurationSeconds: 1, - CPUCFSQuotaPeriod: metav1.Duration{Duration: 50 * time.Millisecond}, - ReservedSystemCPUs: "0-3", - TopologyManagerScope: kubeletconfig.ContainerTopologyManagerScope, - TopologyManagerPolicy: kubeletconfig.NoneTopologyManagerPolicy, - ShutdownGracePeriod: metav1.Duration{Duration: 0}, - ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 0}, - ShutdownGracePeriodByPodPriority: []kubeletconfig.ShutdownGracePeriodByPodPriority{ - { - Priority: 0, - ShutdownGracePeriodSeconds: 10, +func TestValidateKubeletConfiguration(t *testing.T) { + cases := []struct { + name string + configure func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration + errMsg string + }{ + { + name: "Success", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + return conf }, }, - MemorySwap: kubeletconfig.MemorySwapConfiguration{SwapBehavior: kubetypes.UnlimitedSwap}, - MemoryThrottlingFactor: utilpointer.Float64Ptr(0.5), - FeatureGates: map[string]bool{ - "CustomCPUCFSQuotaPeriod": true, - "GracefulNodeShutdown": true, - "GracefulNodeShutdownBasedOnPodPriority": true, - "NodeSwap": true, - "MemoryQoS": true, - }, - Logging: componentbaseconfig.LoggingConfiguration{ - Format: "text", - }, - } - if allErrors := ValidateKubeletConfiguration(successCase3); allErrors != nil { - t.Errorf("expect no errors, got %v", allErrors) - } - - errorCase1 := &kubeletconfig.KubeletConfiguration{ - CgroupsPerQOS: false, - EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved", "illegal-key"}, - SystemCgroups: "/", - CgroupRoot: "", - EventBurst: -10, - EventRecordQPS: -10, - HealthzPort: -10, - ImageGCHighThresholdPercent: 101, - ImageGCLowThresholdPercent: 101, - IPTablesDropBit: -10, - IPTablesMasqueradeBit: -10, - KubeAPIBurst: -10, - KubeAPIQPS: -10, - MaxOpenFiles: -10, - MaxPods: -10, - OOMScoreAdj: -1001, - PodsPerCore: -10, - Port: 0, - ReadOnlyPort: -10, - RegistryBurst: -10, - RegistryPullQPS: -10, - HairpinMode: "foo", - NodeLeaseDurationSeconds: -1, - CPUCFSQuotaPeriod: metav1.Duration{Duration: 100 * time.Millisecond}, - ShutdownGracePeriod: metav1.Duration{Duration: 30 * time.Second}, - ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 60 * time.Second}, - ShutdownGracePeriodByPodPriority: []kubeletconfig.ShutdownGracePeriodByPodPriority{ - { - Priority: 0, - ShutdownGracePeriodSeconds: 10, + { + name: "invalid NodeLeaseDurationSeconds", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.NodeLeaseDurationSeconds = 0 + return conf }, + errMsg: "invalid configuration: nodeLeaseDurationSeconds must be greater than 0", }, - Logging: componentbaseconfig.LoggingConfiguration{ - Format: "", + { + name: "specify EnforceNodeAllocatable without enabling CgroupsPerQOS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.CgroupsPerQOS = false + conf.EnforceNodeAllocatable = []string{"pods"} + return conf + }, + errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) is not supported unless cgroupsPerQOS (--cgroups-per-qos) is set to true", + }, + { + name: "specify SystemCgroups without CgroupRoot", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.SystemCgroups = "/" + conf.CgroupRoot = "" + return conf + }, + errMsg: "invalid configuration: systemCgroups (--system-cgroups) was specified and cgroupRoot (--cgroup-root) was not specified", + }, + { + name: "invalid EventBurst", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EventBurst = -1 + return conf + }, + errMsg: "invalid configuration: eventBurst (--event-burst) -1 must not be a negative number", + }, + { + name: "invalid EventRecordQPS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EventRecordQPS = -1 + return conf + }, + errMsg: "invalid configuration: eventRecordQPS (--event-qps) -1 must not be a negative number", + }, + { + name: "invalid HealthzPort", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.HealthzPort = 65536 + return conf + }, + errMsg: "invalid configuration: healthzPort (--healthz-port) 65536 must be between 1 and 65535, inclusive", + }, + { + name: "specify CPUCFSQuotaPeriod without enabling CPUCFSQuotaPeriod", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": false} + conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 200 * time.Millisecond} + return conf + }, + errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {200ms} requires feature gate CustomCPUCFSQuotaPeriod", + }, + { + name: "invalid CPUCFSQuotaPeriod", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"CustomCPUCFSQuotaPeriod": true} + conf.CPUCFSQuotaPeriod = metav1.Duration{Duration: 2 * time.Second} + return conf + }, + errMsg: "invalid configuration: cpuCFSQuotaPeriod (--cpu-cfs-quota-period) {2s} must be between 1usec and 1sec, inclusive", + }, + { + name: "invalid ImageGCHighThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCHighThresholdPercent = 101 + return conf + }, + errMsg: "invalid configuration: imageGCHighThresholdPercent (--image-gc-high-threshold) 101 must be between 0 and 100, inclusive", + }, + { + name: "invalid ImageGCLowThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCLowThresholdPercent = -1 + return conf + }, + errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) -1 must be between 0 and 100, inclusive", + }, + { + name: "ImageGCLowThresholdPercent is equal to ImageGCHighThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCHighThresholdPercent = 0 + conf.ImageGCLowThresholdPercent = 0 + return conf + }, + errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 0 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", + }, + { + name: "ImageGCLowThresholdPercent is greater than ImageGCHighThresholdPercent", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ImageGCHighThresholdPercent = 0 + conf.ImageGCLowThresholdPercent = 1 + return conf + }, + errMsg: "invalid configuration: imageGCLowThresholdPercent (--image-gc-low-threshold) 1 must be less than imageGCHighThresholdPercent (--image-gc-high-threshold) 0", + }, + { + name: "invalid IPTablesDropBit", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.IPTablesDropBit = 32 + return conf + }, + errMsg: "invalid configuration: iptablesDropBit (--iptables-drop-bit) 32 must be between 0 and 31, inclusive", + }, + { + name: "invalid IPTablesMasqueradeBit", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.IPTablesMasqueradeBit = 32 + return conf + }, + errMsg: "invalid configuration: iptablesMasqueradeBit (--iptables-masquerade-bit) 32 must be between 0 and 31, inclusive", + }, + { + name: "invalid KubeAPIBurst", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.KubeAPIBurst = -1 + return conf + }, + errMsg: "invalid configuration: kubeAPIBurst (--kube-api-burst) -1 must not be a negative number", + }, + { + name: "invalid KubeAPIQPS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.KubeAPIQPS = -1 + return conf + }, + errMsg: "invalid configuration: kubeAPIQPS (--kube-api-qps) -1 must not be a negative number", + }, + { + name: "invalid NodeStatusMaxImages", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.NodeStatusMaxImages = -2 + return conf + }, + errMsg: "invalid configuration: nodeStatusMaxImages (--node-status-max-images) -2 must be -1 or greater", + }, + { + name: "invalid MaxOpenFiles", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxOpenFiles = -1 + return conf + }, + errMsg: "invalid configuration: maxOpenFiles (--max-open-files) -1 must not be a negative number", + }, + { + name: "invalid MaxPods", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MaxPods = -1 + return conf + }, + errMsg: "invalid configuration: maxPods (--max-pods) -1 must not be a negative number", + }, + { + name: "invalid OOMScoreAdj", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.OOMScoreAdj = 1001 + return conf + }, + errMsg: "invalid configuration: oomScoreAdj (--oom-score-adj) 1001 must be between -1000 and 1000, inclusive", + }, + { + name: "invalid PodsPerCore", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.PodsPerCore = -1 + return conf + }, + errMsg: "invalid configuration: podsPerCore (--pods-per-core) -1 must not be a negative number", + }, + { + name: "invalid Port", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.Port = 65536 + return conf + }, + errMsg: "invalid configuration: port (--port) 65536 must be between 1 and 65535, inclusive", + }, + { + name: "invalid ReadOnlyPort", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReadOnlyPort = 65536 + return conf + }, + errMsg: "invalid configuration: readOnlyPort (--read-only-port) 65536 must be between 0 and 65535, inclusive", + }, + { + name: "invalid RegistryBurst", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.RegistryBurst = -1 + return conf + }, + errMsg: "invalid configuration: registryBurst (--registry-burst) -1 must not be a negative number", + }, + { + name: "invalid RegistryPullQPS", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.RegistryPullQPS = -1 + return conf + }, + errMsg: "invalid configuration: registryPullQPS (--registry-qps) -1 must not be a negative number", + }, + { + name: "specify ServerTLSBootstrap without enabling RotateKubeletServerCertificate", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"RotateKubeletServerCertificate": false} + conf.ServerTLSBootstrap = true + return conf + }, + errMsg: "invalid configuration: serverTLSBootstrap true requires feature gate RotateKubeletServerCertificate", + }, + { + name: "use SingleNumaNodeTopologyManagerPolicy without enabling TopologyManager", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"TopologyManager": false} + conf.TopologyManagerPolicy = kubeletconfig.SingleNumaNodeTopologyManagerPolicy + return conf + }, + errMsg: "invalid configuration: topologyManagerPolicy single-numa-node requires feature gate TopologyManager", + }, + { + name: "invalid TopologyManagerPolicy", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.TopologyManagerPolicy = "invalid-policy" + return conf + }, + errMsg: "invalid configuration: topologyManagerPolicy (--topology-manager-policy) \"invalid-policy\" must be one of: [\"none\" \"best-effort\" \"restricted\" \"single-numa-node\"]", + }, + { + name: "use PodTopologyManagerScope without enabling TopologyManager", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"TopologyManager": false} + conf.TopologyManagerScope = kubeletconfig.PodTopologyManagerScope + return conf + }, + errMsg: "invalid configuration: topologyManagerScope pod requires feature gate TopologyManager", + }, + { + name: "invalid TopologyManagerScope", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.TopologyManagerScope = "invalid-scope" + return conf + }, + errMsg: "invalid configuration: topologyManagerScope (--topology-manager-scope) \"invalid-scope\" must be one of: \"container\", or \"pod\"", + }, + { + name: "ShutdownGracePeriodCriticalPods is greater than ShutdownGracePeriod", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} + conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 2 * time.Second} + conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} + return conf + }, + errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {2s} must be <= shutdownGracePeriod {1s}", + }, + { + name: "ShutdownGracePeriod is less than 1 sec", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} + conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Millisecond} + return conf + }, + errMsg: "invalid configuration: shutdownGracePeriod {1ms} must be either zero or otherwise >= 1 sec", + }, + { + name: "ShutdownGracePeriodCriticalPods is less than 1 sec", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": true} + conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Millisecond} + return conf + }, + errMsg: "invalid configuration: shutdownGracePeriodCriticalPods {1ms} must be either zero or otherwise >= 1 sec", + }, + { + name: "specify ShutdownGracePeriod without enabling GracefulNodeShutdown", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} + conf.ShutdownGracePeriod = metav1.Duration{Duration: 1 * time.Second} + return conf + }, + errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", + }, + { + name: "specify ShutdownGracePeriodCriticalPods without enabling GracefulNodeShutdown", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"GracefulNodeShutdown": false} + conf.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: 1 * time.Second} + return conf + }, + errMsg: "invalid configuration: specifying shutdownGracePeriod or shutdownGracePeriodCriticalPods requires feature gate GracefulNodeShutdown", + }, + { + name: "invalid MemorySwap.SwapBehavior", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"NodeSwap": true} + conf.MemorySwap.SwapBehavior = "invalid-behavior" + return conf + }, + errMsg: "invalid configuration: memorySwap.swapBehavior \"invalid-behavior\" must be one of: \"\", \"LimitedSwap\", or \"UnlimitedSwap\"", + }, + { + name: "specify MemorySwap.SwapBehavior without enabling NodeSwap", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"NodeSwap": false} + conf.MemorySwap.SwapBehavior = kubetypes.LimitedSwap + return conf + }, + errMsg: "invalid configuration: memorySwap.swapBehavior cannot be set when NodeSwap feature flag is disabled", + }, + { + name: "specify SystemReservedEnforcementKey without specifying SystemReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{kubetypes.SystemReservedEnforcementKey} + conf.SystemReservedCgroup = "" + return conf + }, + errMsg: "invalid configuration: systemReservedCgroup (--system-reserved-cgroup) must be specified when \"system-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", + }, + { + name: "specify KubeReservedEnforcementKey without specifying KubeReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{kubetypes.KubeReservedEnforcementKey} + conf.KubeReservedCgroup = "" + return conf + }, + errMsg: "invalid configuration: kubeReservedCgroup (--kube-reserved-cgroup) must be specified when \"kube-reserved\" contained in enforceNodeAllocatable (--enforce-node-allocatable)", + }, + { + name: "specify NodeAllocatableNoneKey with additional enforcements", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{kubetypes.NodeAllocatableNoneKey, kubetypes.KubeReservedEnforcementKey} + return conf + }, + errMsg: "invalid configuration: enforceNodeAllocatable (--enforce-node-allocatable) may not contain additional enforcements when \"none\" is specified", + }, + { + name: "invalid EnforceNodeAllocatable", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.EnforceNodeAllocatable = []string{"invalid-enforce-node-allocatable"} + return conf + }, + errMsg: "invalid configuration: option \"invalid-enforce-node-allocatable\" specified for enforceNodeAllocatable (--enforce-node-allocatable). Valid options are \"pods\", \"system-reserved\", \"kube-reserved\", or \"none\"", + }, + { + name: "invalid HairpinMode", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.HairpinMode = "invalid-hair-pin-mode" + return conf + }, + errMsg: "invalid configuration: option \"invalid-hair-pin-mode\" specified for hairpinMode (--hairpin-mode). Valid options are \"none\", \"hairpin-veth\" or \"promiscuous-bridge\"", + }, + { + name: "specify ReservedSystemCPUs with SystemReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReservedSystemCPUs = "0-3" + conf.SystemReservedCgroup = "/system.slice" + return conf + }, + errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", + }, + { + name: "specify ReservedSystemCPUs with KubeReservedCgroup", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReservedSystemCPUs = "0-3" + conf.KubeReservedCgroup = "/system.slice" + return conf + }, + errMsg: "invalid configuration: can't use reservedSystemCPUs (--reserved-cpus) with systemReservedCgroup (--system-reserved-cgroup) or kubeReservedCgroup (--kube-reserved-cgroup)", + }, + { + name: "invalid ReservedSystemCPUs", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.ReservedSystemCPUs = "invalid-reserved-system-cpus" + return conf + }, + errMsg: "invalid configuration: unable to parse reservedSystemCPUs (--reserved-cpus) invalid-reserved-system-cpus, error:", + }, + { + name: "enable MemoryQoS without specifying MemoryThrottlingFactor", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.FeatureGates = map[string]bool{"MemoryQoS": true} + conf.MemoryThrottlingFactor = nil + return conf + }, + errMsg: "invalid configuration: memoryThrottlingFactor is required when MemoryQoS feature flag is enabled", + }, + { + name: "invalid MemoryThrottlingFactor", + configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration { + conf.MemoryThrottlingFactor = utilpointer.Float64(1.1) + return conf + }, + errMsg: "invalid configuration: memoryThrottlingFactor 1.1 must be greater than 0 and less than or equal to 1.0", }, - MemorySwap: kubeletconfig.MemorySwapConfiguration{SwapBehavior: kubetypes.UnlimitedSwap}, - } - const numErrsErrorCase1 = 31 - if allErrors := ValidateKubeletConfiguration(errorCase1); len(allErrors.(utilerrors.Aggregate).Errors()) != numErrsErrorCase1 { - t.Errorf("expect %d errors, got %v", numErrsErrorCase1, len(allErrors.(utilerrors.Aggregate).Errors())) } - errorCase2 := &kubeletconfig.KubeletConfiguration{ - CgroupsPerQOS: true, - EnforceNodeAllocatable: []string{"pods", "system-reserved", "kube-reserved"}, - SystemReservedCgroup: "/system.slice", - KubeReservedCgroup: "/kubelet.service", - SystemCgroups: "", - CgroupRoot: "", - EventBurst: 10, - EventRecordQPS: 5, - HealthzPort: 10248, - ImageGCHighThresholdPercent: 85, - ImageGCLowThresholdPercent: 80, - IPTablesDropBit: 15, - IPTablesMasqueradeBit: 14, - KubeAPIBurst: 10, - KubeAPIQPS: 5, - MaxOpenFiles: 1000000, - MaxPods: 110, - OOMScoreAdj: -999, - PodsPerCore: 100, - Port: 65535, - ReadOnlyPort: 0, - RegistryBurst: 10, - RegistryPullQPS: 5, - HairpinMode: kubeletconfig.PromiscuousBridge, - NodeLeaseDurationSeconds: 1, - CPUCFSQuotaPeriod: metav1.Duration{Duration: 50 * time.Millisecond}, - ReservedSystemCPUs: "0-3", - TopologyManagerScope: "invalid", - TopologyManagerPolicy: "invalid", - ShutdownGracePeriod: metav1.Duration{Duration: 40 * time.Second}, - ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 10 * time.Second}, - MemorySwap: kubeletconfig.MemorySwapConfiguration{SwapBehavior: "invalid"}, - MemoryThrottlingFactor: utilpointer.Float64Ptr(1.1), - FeatureGates: map[string]bool{ - "CustomCPUCFSQuotaPeriod": true, - "GracefulNodeShutdown": true, - "NodeSwap": true, - "MemoryQoS": true, - }, - Logging: componentbaseconfig.LoggingConfiguration{ - Format: "text", - }, - } - const numErrsErrorCase2 = 5 - if allErrors := ValidateKubeletConfiguration(errorCase2); len(allErrors.(utilerrors.Aggregate).Errors()) != numErrsErrorCase2 { - t.Errorf("expect %d errors, got %v", numErrsErrorCase2, len(allErrors.(utilerrors.Aggregate).Errors())) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + errs := validation.ValidateKubeletConfiguration(tc.configure(successConfig.DeepCopy())) + + if len(tc.errMsg) == 0 { + if errs != nil { + t.Errorf("unexpected error: %s", errs) + } + + return + } + + if errs == nil { + t.Errorf("expected error: %s", tc.errMsg) + return + } + + if got := errs.Error(); !strings.Contains(got, tc.errMsg) { + t.Errorf("unexpected error: %s expected to contain %s", got, tc.errMsg) + } + }) } }