kubernetes/pkg/scheduler/apis/config/validation/validation_test.go
aimuz 059acc2f5a
kubernetes components using leader election to leases
Signed-off-by: aimuz <mr.imuz@gmail.com>
2022-11-23 10:59:08 +08:00

1246 lines
34 KiB
Go

/*
Copyright 2018 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
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
componentbaseconfig "k8s.io/component-base/config"
"k8s.io/kubernetes/pkg/scheduler/apis/config"
configv1 "k8s.io/kubernetes/pkg/scheduler/apis/config/v1"
"k8s.io/kubernetes/pkg/scheduler/apis/config/v1beta2"
"k8s.io/kubernetes/pkg/scheduler/apis/config/v1beta3"
"k8s.io/utils/pointer"
)
func TestValidateKubeSchedulerConfigurationV1beta2(t *testing.T) {
podInitialBackoffSeconds := int64(1)
podMaxBackoffSeconds := int64(1)
validConfig := &config.KubeSchedulerConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: v1beta2.SchemeGroupVersion.String(),
},
Parallelism: 8,
ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
AcceptContentTypes: "application/json",
ContentType: "application/json",
QPS: 10,
Burst: 10,
},
LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
ResourceLock: "leases",
LeaderElect: true,
LeaseDuration: metav1.Duration{Duration: 30 * time.Second},
RenewDeadline: metav1.Duration{Duration: 15 * time.Second},
RetryPeriod: metav1.Duration{Duration: 5 * time.Second},
ResourceNamespace: "name",
ResourceName: "name",
},
PodInitialBackoffSeconds: podInitialBackoffSeconds,
PodMaxBackoffSeconds: podMaxBackoffSeconds,
PercentageOfNodesToScore: pointer.Int32(35),
Profiles: []config.KubeSchedulerProfile{
{
SchedulerName: "me",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
Score: config.PluginSet{
Disabled: []config.Plugin{{Name: "*"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100},
},
},
},
{
SchedulerName: "other",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
Bind: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomBind"}},
},
},
},
},
Extenders: []config.Extender{
{
PrioritizeVerb: "prioritize",
Weight: 1,
},
},
}
invalidParallelismValue := validConfig.DeepCopy()
invalidParallelismValue.Parallelism = 0
resourceNameNotSet := validConfig.DeepCopy()
resourceNameNotSet.LeaderElection.ResourceName = ""
resourceNamespaceNotSet := validConfig.DeepCopy()
resourceNamespaceNotSet.LeaderElection.ResourceNamespace = ""
resourceLockNotLeases := validConfig.DeepCopy()
resourceLockNotLeases.LeaderElection.ResourceLock = "configmap"
enableContentProfilingSetWithoutEnableProfiling := validConfig.DeepCopy()
enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false
enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true
metricsBindAddrInvalid := validConfig.DeepCopy()
metricsBindAddrInvalid.MetricsBindAddress = "0.0.0.0:9090"
healthzBindAddrInvalid := validConfig.DeepCopy()
healthzBindAddrInvalid.HealthzBindAddress = "0.0.0.0:9090"
percentageOfNodesToScore101 := validConfig.DeepCopy()
percentageOfNodesToScore101.PercentageOfNodesToScore = pointer.Int32(101)
percentageOfNodesToScoreNegative := validConfig.DeepCopy()
percentageOfNodesToScoreNegative.PercentageOfNodesToScore = pointer.Int32(-1)
schedulerNameNotSet := validConfig.DeepCopy()
schedulerNameNotSet.Profiles[1].SchedulerName = ""
repeatedSchedulerName := validConfig.DeepCopy()
repeatedSchedulerName.Profiles[0].SchedulerName = "other"
profilePercentageOfNodesToScore101 := validConfig.DeepCopy()
profilePercentageOfNodesToScore101.Profiles[1].PercentageOfNodesToScore = pointer.Int32(101)
profilePercentageOfNodesToScoreNegative := validConfig.DeepCopy()
profilePercentageOfNodesToScoreNegative.Profiles[1].PercentageOfNodesToScore = pointer.Int32(-1)
differentQueueSort := validConfig.DeepCopy()
differentQueueSort.Profiles[1].Plugins.QueueSort.Enabled[0].Name = "AnotherSort"
oneEmptyQueueSort := validConfig.DeepCopy()
oneEmptyQueueSort.Profiles[0].Plugins = nil
extenderNegativeWeight := validConfig.DeepCopy()
extenderNegativeWeight.Extenders[0].Weight = -1
invalidNodePercentage := validConfig.DeepCopy()
invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100},
},
}
invalidPluginArgs := validConfig.DeepCopy()
invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.InterPodAffinityArgs{},
},
}
duplicatedPluginConfig := validConfig.DeepCopy()
duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "config",
},
{
Name: "config",
},
}
mismatchQueueSort := validConfig.DeepCopy()
mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{
{
SchedulerName: "me",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "PrioritySort"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "PrioritySort",
},
},
},
{
SchedulerName: "other",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "CustomSort",
},
},
},
}
extenderDuplicateManagedResource := validConfig.DeepCopy()
extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{
{Name: "example.com/foo", IgnoredByScheduler: false},
{Name: "example.com/foo", IgnoredByScheduler: false},
}
extenderDuplicateBind := validConfig.DeepCopy()
extenderDuplicateBind.Extenders[0].BindVerb = "foo"
extenderDuplicateBind.Extenders = append(extenderDuplicateBind.Extenders, config.Extender{
PrioritizeVerb: "prioritize",
BindVerb: "bar",
Weight: 1,
})
validPlugins := validConfig.DeepCopy()
validPlugins.Profiles[0].Plugins.Score.Enabled = append(validPlugins.Profiles[0].Plugins.Score.Enabled, config.Plugin{Name: "PodTopologySpread", Weight: 2})
scenarios := map[string]struct {
config *config.KubeSchedulerConfiguration
wantErrs field.ErrorList
}{
"good": {
config: validConfig,
},
"bad-parallelism-invalid-value": {
config: invalidParallelismValue,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "parallelism",
},
},
},
"bad-resource-name-not-set": {
config: resourceNameNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceName",
},
},
},
"bad-resource-namespace-not-set": {
config: resourceNamespaceNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceNamespace",
},
},
},
"bad-resource-lock-not-leases": {
config: resourceLockNotLeases,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceLock",
},
},
},
"non-empty-metrics-bind-addr": {
config: metricsBindAddrInvalid,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "metricsBindAddress",
},
},
},
"non-empty-healthz-bind-addr": {
config: healthzBindAddrInvalid,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "healthzBindAddress",
},
},
},
"greater-than-100-percentage-of-nodes-to-score": {
config: percentageOfNodesToScore101,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "percentageOfNodesToScore",
},
},
},
"negative-percentage-of-nodes-to-score": {
config: percentageOfNodesToScoreNegative,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "percentageOfNodesToScore",
},
},
},
"scheduler-name-not-set": {
config: schedulerNameNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeRequired,
Field: "profiles[1].schedulerName",
},
},
},
"repeated-scheduler-name": {
config: repeatedSchedulerName,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: "profiles[1].schedulerName",
},
},
},
"greater-than-100-profile-percentage-of-nodes-to-score": {
config: profilePercentageOfNodesToScore101,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"negative-100-profile-percentage-of-nodes-to-score": {
config: profilePercentageOfNodesToScoreNegative,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"different-queue-sort": {
config: differentQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"one-empty-queue-sort": {
config: oneEmptyQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"extender-negative-weight": {
config: extenderNegativeWeight,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders[0].weight",
},
},
},
"extender-duplicate-managed-resources": {
config: extenderDuplicateManagedResource,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders[0].managedResources[1].name",
},
},
},
"extender-duplicate-bind": {
config: extenderDuplicateBind,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders",
},
},
},
"invalid-node-percentage": {
config: invalidNodePercentage,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].pluginConfig[0].args.minCandidateNodesPercentage",
},
},
},
"invalid-plugin-args": {
config: invalidPluginArgs,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].pluginConfig[0].args",
},
},
},
"duplicated-plugin-config": {
config: duplicatedPluginConfig,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: `profiles[0].pluginConfig[1]`,
},
},
},
"mismatch-queue-sort": {
config: mismatchQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"valid-plugins": {
config: validPlugins,
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
errs := ValidateKubeSchedulerConfiguration(scenario.config)
diff := cmp.Diff(scenario.wantErrs.ToAggregate(), errs, ignoreBadValueDetail)
if diff != "" {
t.Errorf("KubeSchedulerConfiguration returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidateKubeSchedulerConfigurationV1beta3(t *testing.T) {
podInitialBackoffSeconds := int64(1)
podMaxBackoffSeconds := int64(1)
validConfig := &config.KubeSchedulerConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: v1beta3.SchemeGroupVersion.String(),
},
Parallelism: 8,
ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
AcceptContentTypes: "application/json",
ContentType: "application/json",
QPS: 10,
Burst: 10,
},
LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
ResourceLock: "leases",
LeaderElect: true,
LeaseDuration: metav1.Duration{Duration: 30 * time.Second},
RenewDeadline: metav1.Duration{Duration: 15 * time.Second},
RetryPeriod: metav1.Duration{Duration: 5 * time.Second},
ResourceNamespace: "name",
ResourceName: "name",
},
PodInitialBackoffSeconds: podInitialBackoffSeconds,
PodMaxBackoffSeconds: podMaxBackoffSeconds,
PercentageOfNodesToScore: pointer.Int32(35),
Profiles: []config.KubeSchedulerProfile{
{
SchedulerName: "me",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
Score: config.PluginSet{
Disabled: []config.Plugin{{Name: "*"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100},
},
},
},
{
SchedulerName: "other",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
Bind: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomBind"}},
},
},
},
},
Extenders: []config.Extender{
{
PrioritizeVerb: "prioritize",
Weight: 1,
},
},
}
invalidParallelismValue := validConfig.DeepCopy()
invalidParallelismValue.Parallelism = 0
resourceNameNotSet := validConfig.DeepCopy()
resourceNameNotSet.LeaderElection.ResourceName = ""
resourceLockNotLeases := validConfig.DeepCopy()
resourceLockNotLeases.LeaderElection.ResourceLock = "configmap"
resourceNamespaceNotSet := validConfig.DeepCopy()
resourceNamespaceNotSet.LeaderElection.ResourceNamespace = ""
enableContentProfilingSetWithoutEnableProfiling := validConfig.DeepCopy()
enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false
enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true
metricsBindAddrInvalid := validConfig.DeepCopy()
metricsBindAddrInvalid.MetricsBindAddress = "0.0.0.0:9090"
healthzBindAddrInvalid := validConfig.DeepCopy()
healthzBindAddrInvalid.HealthzBindAddress = "0.0.0.0:9090"
percentageOfNodesToScore101 := validConfig.DeepCopy()
percentageOfNodesToScore101.PercentageOfNodesToScore = pointer.Int32(101)
percentageOfNodesToScoreNegative := validConfig.DeepCopy()
percentageOfNodesToScoreNegative.Profiles[1].PercentageOfNodesToScore = pointer.Int32(-1)
schedulerNameNotSet := validConfig.DeepCopy()
schedulerNameNotSet.Profiles[1].SchedulerName = ""
repeatedSchedulerName := validConfig.DeepCopy()
repeatedSchedulerName.Profiles[0].SchedulerName = "other"
profilePercentageOfNodesToScore101 := validConfig.DeepCopy()
profilePercentageOfNodesToScore101.Profiles[1].PercentageOfNodesToScore = pointer.Int32(101)
profilePercentageOfNodesToScoreNegative := validConfig.DeepCopy()
profilePercentageOfNodesToScoreNegative.Profiles[1].PercentageOfNodesToScore = pointer.Int32(-1)
differentQueueSort := validConfig.DeepCopy()
differentQueueSort.Profiles[1].Plugins.QueueSort.Enabled[0].Name = "AnotherSort"
oneEmptyQueueSort := validConfig.DeepCopy()
oneEmptyQueueSort.Profiles[0].Plugins = nil
extenderNegativeWeight := validConfig.DeepCopy()
extenderNegativeWeight.Extenders[0].Weight = -1
invalidNodePercentage := validConfig.DeepCopy()
invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100},
},
}
invalidPluginArgs := validConfig.DeepCopy()
invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.InterPodAffinityArgs{},
},
}
duplicatedPlugins := validConfig.DeepCopy()
duplicatedPlugins.Profiles[0].Plugins.PreEnqueue.Enabled = []config.Plugin{
{Name: "CustomPreEnqueue"},
{Name: "CustomPreEnqueue"},
}
duplicatedPluginConfig := validConfig.DeepCopy()
duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "config",
},
{
Name: "config",
},
}
mismatchQueueSort := validConfig.DeepCopy()
mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{
{
SchedulerName: "me",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "PrioritySort"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "PrioritySort",
},
},
},
{
SchedulerName: "other",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "CustomSort",
},
},
},
}
extenderDuplicateManagedResource := validConfig.DeepCopy()
extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{
{Name: "example.com/foo", IgnoredByScheduler: false},
{Name: "example.com/foo", IgnoredByScheduler: false},
}
extenderDuplicateBind := validConfig.DeepCopy()
extenderDuplicateBind.Extenders[0].BindVerb = "foo"
extenderDuplicateBind.Extenders = append(extenderDuplicateBind.Extenders, config.Extender{
PrioritizeVerb: "prioritize",
BindVerb: "bar",
Weight: 1,
})
validPlugins := validConfig.DeepCopy()
validPlugins.Profiles[0].Plugins.Score.Enabled = append(validPlugins.Profiles[0].Plugins.Score.Enabled, config.Plugin{Name: "PodTopologySpread", Weight: 2})
scenarios := map[string]struct {
config *config.KubeSchedulerConfiguration
wantErrs field.ErrorList
}{
"good": {
config: validConfig,
},
"bad-parallelism-invalid-value": {
config: invalidParallelismValue,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "parallelism",
},
},
},
"bad-resource-name-not-set": {
config: resourceNameNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceName",
},
},
},
"bad-resource-lock-not-leases": {
config: resourceLockNotLeases,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceLock",
},
},
},
"bad-resource-namespace-not-set": {
config: resourceNamespaceNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceNamespace",
},
},
},
"non-empty-metrics-bind-addr": {
config: metricsBindAddrInvalid,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "metricsBindAddress",
},
},
},
"non-empty-healthz-bind-addr": {
config: healthzBindAddrInvalid,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "healthzBindAddress",
},
},
},
"bad-percentage-of-nodes-to-score": {
config: percentageOfNodesToScore101,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "percentageOfNodesToScore",
},
},
},
"negative-percentage-of-nodes-to-score": {
config: percentageOfNodesToScoreNegative,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"scheduler-name-not-set": {
config: schedulerNameNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeRequired,
Field: "profiles[1].schedulerName",
},
},
},
"repeated-scheduler-name": {
config: repeatedSchedulerName,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: "profiles[1].schedulerName",
},
},
},
"greater-than-100-profile-percentage-of-nodes-to-score": {
config: profilePercentageOfNodesToScore101,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"negative-100-profile-percentage-of-nodes-to-score": {
config: profilePercentageOfNodesToScoreNegative,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"different-queue-sort": {
config: differentQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"one-empty-queue-sort": {
config: oneEmptyQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"extender-negative-weight": {
config: extenderNegativeWeight,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders[0].weight",
},
},
},
"extender-duplicate-managed-resources": {
config: extenderDuplicateManagedResource,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders[0].managedResources[1].name",
},
},
},
"extender-duplicate-bind": {
config: extenderDuplicateBind,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders",
},
},
},
"invalid-node-percentage": {
config: invalidNodePercentage,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].pluginConfig[0].args.minCandidateNodesPercentage",
},
},
},
"invalid-plugin-args": {
config: invalidPluginArgs,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].pluginConfig[0].args",
},
},
},
"duplicated-plugin-config": {
config: duplicatedPluginConfig,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: "profiles[0].pluginConfig[1]",
},
},
},
"mismatch-queue-sort": {
config: mismatchQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"valid-plugins": {
config: validPlugins,
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
errs := ValidateKubeSchedulerConfiguration(scenario.config)
diff := cmp.Diff(scenario.wantErrs.ToAggregate(), errs, ignoreBadValueDetail)
if diff != "" {
t.Errorf("KubeSchedulerConfiguration returned err (-want,+got):\n%s", diff)
}
})
}
}
func TestValidateKubeSchedulerConfigurationV1(t *testing.T) {
podInitialBackoffSeconds := int64(1)
podMaxBackoffSeconds := int64(1)
validConfig := &config.KubeSchedulerConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: configv1.SchemeGroupVersion.String(),
},
Parallelism: 8,
ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
AcceptContentTypes: "application/json",
ContentType: "application/json",
QPS: 10,
Burst: 10,
},
LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
ResourceLock: "leases",
LeaderElect: true,
LeaseDuration: metav1.Duration{Duration: 30 * time.Second},
RenewDeadline: metav1.Duration{Duration: 15 * time.Second},
RetryPeriod: metav1.Duration{Duration: 5 * time.Second},
ResourceNamespace: "name",
ResourceName: "name",
},
PodInitialBackoffSeconds: podInitialBackoffSeconds,
PodMaxBackoffSeconds: podMaxBackoffSeconds,
Profiles: []config.KubeSchedulerProfile{
{
SchedulerName: "me",
PercentageOfNodesToScore: pointer.Int32(35),
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
Score: config.PluginSet{
Disabled: []config.Plugin{{Name: "*"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100},
},
},
},
{
SchedulerName: "other",
PercentageOfNodesToScore: pointer.Int32(35),
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
Bind: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomBind"}},
},
},
},
},
Extenders: []config.Extender{
{
PrioritizeVerb: "prioritize",
Weight: 1,
},
},
}
invalidParallelismValue := validConfig.DeepCopy()
invalidParallelismValue.Parallelism = 0
resourceNameNotSet := validConfig.DeepCopy()
resourceNameNotSet.LeaderElection.ResourceName = ""
resourceNamespaceNotSet := validConfig.DeepCopy()
resourceNamespaceNotSet.LeaderElection.ResourceNamespace = ""
resourceLockNotLeases := validConfig.DeepCopy()
resourceLockNotLeases.LeaderElection.ResourceLock = "configmap"
enableContentProfilingSetWithoutEnableProfiling := validConfig.DeepCopy()
enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false
enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true
metricsBindAddrInvalid := validConfig.DeepCopy()
metricsBindAddrInvalid.MetricsBindAddress = "0.0.0.0:9090"
healthzBindAddrInvalid := validConfig.DeepCopy()
healthzBindAddrInvalid.HealthzBindAddress = "0.0.0.0:9090"
percentageOfNodesToScore101 := validConfig.DeepCopy()
percentageOfNodesToScore101.PercentageOfNodesToScore = pointer.Int32(101)
percentageOfNodesToScoreNegative := validConfig.DeepCopy()
percentageOfNodesToScoreNegative.PercentageOfNodesToScore = pointer.Int32(-1)
schedulerNameNotSet := validConfig.DeepCopy()
schedulerNameNotSet.Profiles[1].SchedulerName = ""
repeatedSchedulerName := validConfig.DeepCopy()
repeatedSchedulerName.Profiles[0].SchedulerName = "other"
profilePercentageOfNodesToScore101 := validConfig.DeepCopy()
profilePercentageOfNodesToScore101.Profiles[1].PercentageOfNodesToScore = pointer.Int32(101)
profilePercentageOfNodesToScoreNegative := validConfig.DeepCopy()
profilePercentageOfNodesToScoreNegative.Profiles[1].PercentageOfNodesToScore = pointer.Int32(-1)
differentQueueSort := validConfig.DeepCopy()
differentQueueSort.Profiles[1].Plugins.QueueSort.Enabled[0].Name = "AnotherSort"
oneEmptyQueueSort := validConfig.DeepCopy()
oneEmptyQueueSort.Profiles[0].Plugins = nil
extenderNegativeWeight := validConfig.DeepCopy()
extenderNegativeWeight.Extenders[0].Weight = -1
invalidNodePercentage := validConfig.DeepCopy()
invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100},
},
}
invalidPluginArgs := validConfig.DeepCopy()
invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "DefaultPreemption",
Args: &config.InterPodAffinityArgs{},
},
}
duplicatedPluginConfig := validConfig.DeepCopy()
duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{
{
Name: "config",
},
{
Name: "config",
},
}
mismatchQueueSort := validConfig.DeepCopy()
mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{
{
SchedulerName: "me",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "PrioritySort"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "PrioritySort",
},
},
},
{
SchedulerName: "other",
Plugins: &config.Plugins{
QueueSort: config.PluginSet{
Enabled: []config.Plugin{{Name: "CustomSort"}},
},
},
PluginConfig: []config.PluginConfig{
{
Name: "CustomSort",
},
},
},
}
extenderDuplicateManagedResource := validConfig.DeepCopy()
extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{
{Name: "example.com/foo", IgnoredByScheduler: false},
{Name: "example.com/foo", IgnoredByScheduler: false},
}
extenderDuplicateBind := validConfig.DeepCopy()
extenderDuplicateBind.Extenders[0].BindVerb = "foo"
extenderDuplicateBind.Extenders = append(extenderDuplicateBind.Extenders, config.Extender{
PrioritizeVerb: "prioritize",
BindVerb: "bar",
Weight: 1,
})
validPlugins := validConfig.DeepCopy()
validPlugins.Profiles[0].Plugins.Score.Enabled = append(validPlugins.Profiles[0].Plugins.Score.Enabled, config.Plugin{Name: "PodTopologySpread", Weight: 2})
invalidPlugins := validConfig.DeepCopy()
invalidPlugins.Profiles[0].Plugins.Score.Enabled = append(invalidPlugins.Profiles[0].Plugins.Score.Enabled, config.Plugin{Name: "SelectorSpread"})
scenarios := map[string]struct {
config *config.KubeSchedulerConfiguration
wantErrs field.ErrorList
}{
"good": {
config: validConfig,
},
"bad-parallelism-invalid-value": {
config: invalidParallelismValue,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "parallelism",
},
},
},
"bad-resource-name-not-set": {
config: resourceNameNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceName",
},
},
},
"bad-resource-namespace-not-set": {
config: resourceNamespaceNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceNamespace",
},
},
},
"bad-resource-lock-not-leases": {
config: resourceLockNotLeases,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "leaderElection.resourceLock",
},
},
},
"non-empty-metrics-bind-addr": {
config: metricsBindAddrInvalid,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "metricsBindAddress",
},
},
},
"non-empty-healthz-bind-addr": {
config: healthzBindAddrInvalid,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "healthzBindAddress",
},
},
},
"bad-percentage-of-nodes-to-score": {
config: percentageOfNodesToScore101,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "percentageOfNodesToScore",
},
},
},
"negative-percentage-of-nodes-to-score": {
config: percentageOfNodesToScoreNegative,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "percentageOfNodesToScore",
},
},
},
"scheduler-name-not-set": {
config: schedulerNameNotSet,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeRequired,
Field: "profiles[1].schedulerName",
},
},
},
"repeated-scheduler-name": {
config: repeatedSchedulerName,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: "profiles[1].schedulerName",
},
},
},
"greater-than-100-profile-percentage-of-nodes-to-score": {
config: profilePercentageOfNodesToScore101,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"negative-profile-percentage-of-nodes-to-score": {
config: profilePercentageOfNodesToScoreNegative,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].percentageOfNodesToScore",
},
},
},
"different-queue-sort": {
config: differentQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"one-empty-queue-sort": {
config: oneEmptyQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"extender-negative-weight": {
config: extenderNegativeWeight,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders[0].weight",
},
},
},
"extender-duplicate-managed-resources": {
config: extenderDuplicateManagedResource,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders[0].managedResources[1].name",
},
},
},
"extender-duplicate-bind": {
config: extenderDuplicateBind,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "extenders",
},
},
},
"invalid-node-percentage": {
config: invalidNodePercentage,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].pluginConfig[0].args.minCandidateNodesPercentage",
},
},
},
"invalid-plugin-args": {
config: invalidPluginArgs,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].pluginConfig[0].args",
},
},
},
"duplicated-plugin-config": {
config: duplicatedPluginConfig,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeDuplicate,
Field: "profiles[0].pluginConfig[1]",
},
},
},
"mismatch-queue-sort": {
config: mismatchQueueSort,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[1].plugins.queueSort",
},
},
},
"invalid-plugins": {
config: invalidPlugins,
wantErrs: field.ErrorList{
&field.Error{
Type: field.ErrorTypeInvalid,
Field: "profiles[0].plugins.score.enabled[0]",
},
},
},
"valid-plugins": {
config: validPlugins,
},
}
for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
errs := ValidateKubeSchedulerConfiguration(scenario.config)
diff := cmp.Diff(scenario.wantErrs.ToAggregate(), errs, ignoreBadValueDetail)
if diff != "" {
t.Errorf("KubeSchedulerConfiguration returned err (-want,+got):\n%s", diff)
}
})
}
}