kubernetes/pkg/kubelet/apis/config/validation/validation_test.go
Patrick Ohly ea3f25f49b logs: add alpha+beta feature gates
It is useful to have the ability to control whether alpha or beta features are
enabled. We can group features under LoggingAlphaOptions and LoggingBetaOptions
because the configuration is designed so that each feature individually must be
enabled via its own option.

Currently, the JSON format itself is beta (graduated in 1.23) but additional
options for it were only added in 1.23 and thus are still alpha:

  $ go run ./staging/src/k8s.io/component-base/logs/example/cmd/logger.go --logging-format=json --log-json-split-stream --log-json-info-buffer-size 1M --feature-gates LoggingBetaOptions=false
  [format: Forbidden: Log format json is BETA and disabled, see LoggingBetaOptions feature, options.json.splitStream: Forbidden: Feature LoggingAlphaOptions is disabled, options.json.infoBufferSize: Forbidden: Feature LoggingAlphaOptions is disabled]

  $ go run ./staging/src/k8s.io/component-base/logs/example/cmd/logger.go --logging-format=json --log-json-split-stream --log-json-info-buffer-size 1M
  [options.json.splitStream: Forbidden: Feature LoggingAlphaOptions is disabled, options.json.infoBufferSize: Forbidden: Feature LoggingAlphaOptions is disabled]

This is the same approach that was taken for CPUManagerPolicyAlphaOptions and
CPUManagerPolicyBetaOptions.

In order to test this without modifying the global feature gate in a test file,
ValidateKubeletConfiguration must take a feature gate as argument.
2022-06-17 20:22:13 +02:00

525 lines
22 KiB
Go

/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation_test
import (
"strings"
"testing"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
logsapi "k8s.io/component-base/logs/api/v1"
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"
)
var (
successConfig = 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: 25 * time.Millisecond},
TopologyManagerScope: kubeletconfig.PodTopologyManagerScope,
TopologyManagerPolicy: kubeletconfig.SingleNumaNodeTopologyManagerPolicy,
ShutdownGracePeriod: metav1.Duration{Duration: 30 * time.Second},
ShutdownGracePeriodCriticalPods: metav1.Duration{Duration: 10 * time.Second},
MemoryThrottlingFactor: utilpointer.Float64Ptr(0.8),
FeatureGates: map[string]bool{
"CustomCPUCFSQuotaPeriod": true,
"GracefulNodeShutdown": true,
"MemoryQoS": true,
},
Logging: logsapi.LoggingConfiguration{
Format: "text",
},
}
)
func TestValidateKubeletConfiguration(t *testing.T) {
featureGate := utilfeature.DefaultFeatureGate.DeepCopy()
logsapi.AddFeatureGates(featureGate)
cases := []struct {
name string
configure func(config *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration
errMsg string
}{
{
name: "Success",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
return conf
},
},
{
name: "invalid NodeLeaseDurationSeconds",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
conf.NodeLeaseDurationSeconds = 0
return conf
},
errMsg: "invalid configuration: nodeLeaseDurationSeconds must be greater than 0",
},
{
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",
},
{
name: "invalid Taint.TimeAdded",
configure: func(conf *kubeletconfig.KubeletConfiguration) *kubeletconfig.KubeletConfiguration {
now := metav1.Now()
conf.RegisterWithTaints = []v1.Taint{{TimeAdded: &now}}
return conf
},
errMsg: "invalid configuration: taint.TimeAdded is not nil",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
errs := validation.ValidateKubeletConfiguration(tc.configure(successConfig.DeepCopy()), featureGate)
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)
}
})
}
}