/* Copyright 2022 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 v1 import ( "testing" "github.com/google/go-cmp/cmp" "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/klog/v2/ktesting" v1 "k8s.io/kube-scheduler/config/v1" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" "k8s.io/utils/pointer" ) func TestApplyFeatureGates(t *testing.T) { tests := []struct { name string features map[featuregate.Feature]bool wantConfig *v1.Plugins }{ { name: "Feature gates disabled", wantConfig: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: names.PrioritySort}, {Name: names.NodeUnschedulable}, {Name: names.NodeName}, {Name: names.TaintToleration, Weight: pointer.Int32(3)}, {Name: names.NodeAffinity, Weight: pointer.Int32(2)}, {Name: names.NodePorts}, {Name: names.NodeResourcesFit, Weight: pointer.Int32(1)}, {Name: names.VolumeRestrictions}, {Name: names.EBSLimits}, {Name: names.GCEPDLimits}, {Name: names.NodeVolumeLimits}, {Name: names.AzureDiskLimits}, {Name: names.VolumeBinding}, {Name: names.VolumeZone}, {Name: names.PodTopologySpread, Weight: pointer.Int32(2)}, {Name: names.InterPodAffinity, Weight: pointer.Int32(2)}, {Name: names.DefaultPreemption}, {Name: names.NodeResourcesBalancedAllocation, Weight: pointer.Int32(1)}, {Name: names.ImageLocality, Weight: pointer.Int32(1)}, {Name: names.DefaultBinder}, }, }, }, }, { name: "Feature gate PodSchedulingReadiness enabled", features: map[featuregate.Feature]bool{ features.PodSchedulingReadiness: true, }, wantConfig: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: names.PrioritySort}, {Name: names.NodeUnschedulable}, {Name: names.NodeName}, {Name: names.TaintToleration, Weight: pointer.Int32(3)}, {Name: names.NodeAffinity, Weight: pointer.Int32(2)}, {Name: names.NodePorts}, {Name: names.NodeResourcesFit, Weight: pointer.Int32(1)}, {Name: names.VolumeRestrictions}, {Name: names.EBSLimits}, {Name: names.GCEPDLimits}, {Name: names.NodeVolumeLimits}, {Name: names.AzureDiskLimits}, {Name: names.VolumeBinding}, {Name: names.VolumeZone}, {Name: names.PodTopologySpread, Weight: pointer.Int32(2)}, {Name: names.InterPodAffinity, Weight: pointer.Int32(2)}, {Name: names.DefaultPreemption}, {Name: names.NodeResourcesBalancedAllocation, Weight: pointer.Int32(1)}, {Name: names.ImageLocality, Weight: pointer.Int32(1)}, {Name: names.DefaultBinder}, {Name: names.SchedulingGates}, }, }, }, }, { name: "Feature gate DynamicResourceAllocation enabled", features: map[featuregate.Feature]bool{ features.DynamicResourceAllocation: true, }, wantConfig: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: names.PrioritySort}, {Name: names.NodeUnschedulable}, {Name: names.NodeName}, {Name: names.TaintToleration, Weight: pointer.Int32(3)}, {Name: names.NodeAffinity, Weight: pointer.Int32(2)}, {Name: names.NodePorts}, {Name: names.NodeResourcesFit, Weight: pointer.Int32(1)}, {Name: names.VolumeRestrictions}, {Name: names.EBSLimits}, {Name: names.GCEPDLimits}, {Name: names.NodeVolumeLimits}, {Name: names.AzureDiskLimits}, {Name: names.VolumeBinding}, {Name: names.VolumeZone}, {Name: names.PodTopologySpread, Weight: pointer.Int32(2)}, {Name: names.InterPodAffinity, Weight: pointer.Int32(2)}, {Name: names.DynamicResources}, {Name: names.DefaultPreemption}, {Name: names.NodeResourcesBalancedAllocation, Weight: pointer.Int32(1)}, {Name: names.ImageLocality, Weight: pointer.Int32(1)}, {Name: names.DefaultBinder}, }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { for k, v := range test.features { defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, k, v)() } gotConfig := getDefaultPlugins() if diff := cmp.Diff(test.wantConfig, gotConfig); diff != "" { t.Errorf("unexpected config diff (-want, +got): %s", diff) } }) } } func TestMergePlugins(t *testing.T) { tests := []struct { name string customPlugins *v1.Plugins defaultPlugins *v1.Plugins expectedPlugins *v1.Plugins }{ { name: "AppendCustomPlugin", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, {Name: "CustomPlugin"}, }, }, }, }, { name: "InsertAfterDefaultPlugins2", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, {Name: "DefaultPlugin2"}, }, Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "CustomPlugin"}, {Name: "DefaultPlugin2"}, }, Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, }, }, { name: "InsertBeforeAllPlugins", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, Disabled: []v1.Plugin{ {Name: "*"}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, Disabled: []v1.Plugin{ {Name: "*"}, }, }, }, }, { name: "ReorderDefaultPlugins", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, {Name: "DefaultPlugin1"}, }, Disabled: []v1.Plugin{ {Name: "*"}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, {Name: "DefaultPlugin1"}, }, Disabled: []v1.Plugin{ {Name: "*"}, }, }, }, }, { name: "ApplyNilCustomPlugin", customPlugins: nil, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, }, { name: "CustomPluginOverrideDefaultPlugin", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1", Weight: pointer.Int32(2)}, {Name: "Plugin3", Weight: pointer.Int32(3)}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1"}, {Name: "Plugin2"}, {Name: "Plugin3"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1", Weight: pointer.Int32(2)}, {Name: "Plugin2"}, {Name: "Plugin3", Weight: pointer.Int32(3)}, }, }, }, }, { name: "OrderPreserveAfterOverride", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin2", Weight: pointer.Int32(2)}, {Name: "Plugin1", Weight: pointer.Int32(1)}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1"}, {Name: "Plugin2"}, {Name: "Plugin3"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1", Weight: pointer.Int32(1)}, {Name: "Plugin2", Weight: pointer.Int32(2)}, {Name: "Plugin3"}, }, }, }, }, { name: "RepeatedCustomPlugin", customPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1"}, {Name: "Plugin2", Weight: pointer.Int32(2)}, {Name: "Plugin3"}, {Name: "Plugin2", Weight: pointer.Int32(4)}, }, }, }, defaultPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1"}, {Name: "Plugin2"}, {Name: "Plugin3"}, }, }, }, expectedPlugins: &v1.Plugins{ Filter: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "Plugin1"}, {Name: "Plugin2", Weight: pointer.Int32(4)}, {Name: "Plugin3"}, {Name: "Plugin2", Weight: pointer.Int32(2)}, }, }, }, }, { name: "Append custom MultiPoint plugin", customPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, }, }, }, defaultPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, {Name: "CustomPlugin"}, }, }, }, }, { name: "Append disabled Multipoint plugins", customPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, {Name: "CustomPlugin2"}, }, Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, Score: v1.PluginSet{ Disabled: []v1.Plugin{ {Name: "CustomPlugin2"}, }, }, }, defaultPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "DefaultPlugin2"}, }, }, }, expectedPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin1"}, {Name: "CustomPlugin"}, {Name: "CustomPlugin2"}, }, Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, Score: v1.PluginSet{ Disabled: []v1.Plugin{ {Name: "CustomPlugin2"}, }, }, }, }, { name: "override default MultiPoint plugins with custom value", customPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin", Weight: pointer.Int32(5)}, }, }, }, defaultPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin"}, }, }, }, expectedPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin", Weight: pointer.Int32(5)}, }, }, }, }, { name: "disabled MultiPoint plugin in default set", defaultPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin"}, }, Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, }, customPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, }, }, }, expectedPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin"}, {Name: "CustomPlugin"}, }, Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, }, }, { name: "disabled MultiPoint plugin in default set for specific extension point", defaultPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin"}, }, }, Score: v1.PluginSet{ Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, }, customPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "CustomPlugin"}, }, }, }, expectedPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin"}, {Name: "CustomPlugin"}, }, }, Score: v1.PluginSet{ Disabled: []v1.Plugin{ {Name: "DefaultPlugin2"}, }, }, }, }, { name: "multipoint with only disabled gets merged", defaultPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Enabled: []v1.Plugin{ {Name: "DefaultPlugin"}, }, }, }, customPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Disabled: []v1.Plugin{ {Name: "DefaultPlugin"}, }, }, }, expectedPlugins: &v1.Plugins{ MultiPoint: v1.PluginSet{ Disabled: []v1.Plugin{ {Name: "DefaultPlugin"}, }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { logger, _ := ktesting.NewTestContext(t) gotPlugins := mergePlugins(logger, test.defaultPlugins, test.customPlugins) if d := cmp.Diff(test.expectedPlugins, gotPlugins); d != "" { t.Fatalf("plugins mismatch (-want +got):\n%s", d) } }) } }