Change:
has compatibility tests for Policy v1. We are lacking similar tests for other options of , such as: [x] : This can be right away to test that default plugins can be disabled, changed order, etc. [x] : This one is trickier, as it requires a new method in to expose all configurations and a new method in so that they can say which args they used. /sig scheduling /help /kind cleanup
/assign
you can work on the first one. I'll have a PR soon that should clarify how the second one should be done.
I have sent a pr for the first one. And I'm working on the second one, I think it's better to keep 2 PRs for this issue? If so, PTAL at the first one, thanks.
Yes, 2 PRs is ideal. I'll review later today.
I have one small question about do I need to mock some fake plugins for testing , or just testing the default plugins?
You should use the existing plugins, because these are compatibility tests.
Okay
Actually, I think adding a new method such as to interface is a little redundant. In fact, most plugins don't need any argument for building them, so we are talking about adding a new method that needs to be implemented by all of the existing and coming plugins. And users who write out-of-tree plugins also hardly have the need for using this method. IMO, it would be more meaningful to move up to the interface. Then by this method, we can simply implement for default plugins who actually need args. WDYT?
should remain unexported because it provides pointers. However, I agree that might be too much to ask for all plugins. Instead, we can add a new interface and then plugins can opt-int. Not sure if we should follow this extension pattern Any thoughts
On the other hand, simply returning doesn't seem like much.
Sure, then I will add like what we want before(add this method to interface), and if there is any better idea later, please let me know.
I think we should just add it to the plugins that actually have args, this will require custom code in the test, but I think it is better than having all plugins implement a function means something to only 1% of them.
Thanks, but now there is a problem that we can't get the registered plugins with current API. So, I'm not sure if we can just implement the method in with some reflect code for checking if one plugin has implement method? This could help us with keeping the interface clean. cc
you don't need to check if it implements BuildArgs, since this is for testing, we can hardcode which plugins should have their args verified.
The test will not be pretty, but I think it is worth the tradeoff.
Do you mean that we don't test the default plugins, but define some custom plugins in the test file? And we don't need the ListPluginArgs method anymore, am I right?
and I discussed this offline and arrived to this conclusion: For the compatiblity test, we can mock ( in ) to capture the arguments the configuration that is being passed. This can also be used by with We want another test for that checks the build args for the in-tree plugins accessing the internal pointers. These 2 tests should cover the whole stack. Sorry for the back-and-forth. Sometimes it's useful to see a full PR to realize that the initial idea wasn't good enough. Thanks a lot for it.
Sure, that sounds much more clear, thanks. :D
One more question, the is under package , and the compatibility test is under package . So for mocking , we need to change to a function variable in , and add a method for it, right It will look like: Maybe we can add something like to , but that's the implementation details. I want to make sure if the above design is proper.
Or rather, you could add something like: . Then you can call the capturer from if there is one defined.
Got your message in slack, let me reply here. sounds good, and do I need to revert the change in ? I think is not necessary now.
I think they will still be useful for the other test:
Hello are you still working on this?
Ah, I'm so sorry that I'm so busy in the last few weeks, but I will send prs fot this issue in this weekend. Sorry for my delay again.

Discussion:
v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" <ins> "k8s.io/apimachinery/pkg/runtime" </ins> utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake"

} func TestPluginsConfigurationCompatibility(t *testing.T) { <ins> defaultPlugins := map[string][]config.Plugin{ "QueueSortPlugin": { {Name: "PrioritySort"}, }, "PreFilterPlugin": { {Name: "NodeResourcesFit"}, {Name: "NodePorts"}, {Name: "InterPodAffinity"}, {Name: "PodTopologySpread"}, }, "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeResourcesFit"}, {Name: "NodeName"}, {Name: "NodePorts"}, {Name: "NodeAffinity"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "EBSLimits"}, {Name: "GCEPDLimits"}, {Name: "NodeVolumeLimits"}, {Name: "AzureDiskLimits"}, {Name: "VolumeBinding"}, {Name: "VolumeZone"}, {Name: "InterPodAffinity"}, {Name: "PodTopologySpread"}, }, "PreScorePlugin": { {Name: "InterPodAffinity"}, {Name: "DefaultPodTopologySpread"}, {Name: "TaintToleration"}, {Name: "PodTopologySpread"}, }, "ScorePlugin": { {Name: "NodeResourcesBalancedAllocation", Weight: 1}, {Name: "ImageLocality", Weight: 1}, {Name: "InterPodAffinity", Weight: 1}, {Name: "NodeResourcesLeastAllocated", Weight: 1}, {Name: "NodeAffinity", Weight: 1}, {Name: "NodePreferAvoidPods", Weight: 10000}, {Name: "DefaultPodTopologySpread", Weight: 1}, {Name: "TaintToleration", Weight: 1}, {Name: "PodTopologySpread", Weight: 1}, }, "BindPlugin": {{Name: "DefaultBinder"}}, } </ins> testcases := []struct { <del> name        string plugins     config.Plugins wantPlugins map[string][]config.Plugin </del> <ins> name         string plugins      config.Plugins wantPlugins  map[string][]config.Plugin pluginConfig []config.PluginConfig </ins> }{ { <del> name: "No plugins specified", wantPlugins: map[string][]config.Plugin{ "QueueSortPlugin": { {Name: "PrioritySort"}, </del> <ins> name:        "default plugins", wantPlugins: defaultPlugins, }, { name:        "default plugins with customized plugin config", wantPlugins: defaultPlugins, pluginConfig: []config.PluginConfig{ { Name: "NodeResourcesFit", Args: runtime.Unknown{ Raw: []byte(`{ "ignoredResources": [ "foo", "bar" ] }`), }, </ins> }, <del> "PreFilterPlugin": { {Name: "NodeResourcesFit"}, {Name: "NodePorts"}, {Name: "InterPodAffinity"}, {Name: "PodTopologySpread"}, </del> <ins> { Name: "PodTopologySpread", Args: runtime.Unknown{ Raw: []byte(`{ "defaultConstraints": [ { "maxSkew": 1, "topologyKey": "foo", "whenUnsatisfiable": "DoNotSchedule" }, { "maxSkew": 10, "topologyKey": "bar", "whenUnsatisfiable": "ScheduleAnyway" } ] }`), }, </ins> }, <del> "FilterPlugin": { {Name: "NodeUnschedulable"}, {Name: "NodeResourcesFit"}, {Name: "NodeName"}, {Name: "NodePorts"}, {Name: "NodeAffinity"}, {Name: "VolumeRestrictions"}, {Name: "TaintToleration"}, {Name: "EBSLimits"}, {Name: "GCEPDLimits"}, {Name: "NodeVolumeLimits"}, {Name: "AzureDiskLimits"}, {Name: "VolumeBinding"}, {Name: "VolumeZone"}, {Name: "InterPodAffinity"}, {Name: "PodTopologySpread"}, </del> <ins> { Name: "RequestedToCapacityRatio", Args: runtime.Unknown{ Raw: []byte(`{ "shape":[ "Utilization": 5, "Score": 5 ], "resources":[ "Name": "cpu", "Weight": 10 ] }`), }, </ins> }, <del> "PreScorePlugin": { {Name: "InterPodAffinity"}, {Name: "DefaultPodTopologySpread"}, {Name: "TaintToleration"}, {Name: "PodTopologySpread"}, </del> <ins> { Name: "InterPodAffinity", Args: runtime.Unknown{ Raw: []byte(`{ "HardPodAffinityWeight": 100 }`), }, </ins> }, <del> "ScorePlugin": { {Name: "NodeResourcesBalancedAllocation", Weight: 1}, {Name: "ImageLocality", Weight: 1}, {Name: "InterPodAffinity", Weight: 1}, {Name: "NodeResourcesLeastAllocated", Weight: 1}, {Name: "NodeAffinity", Weight: 1}, {Name: "NodePreferAvoidPods", Weight: 10000}, {Name: "DefaultPodTopologySpread", Weight: 1}, {Name: "TaintToleration", Weight: 1}, {Name: "PodTopologySpread", Weight: 1}, </del> <ins> { Name: "NodeLabel", Args: runtime.Unknown{ Raw: []byte(`{ "presentLabels": [ "foo", "bar" ], "absentLabels": [ "apple" ], "presentLabelsPreference": [ "dog" ], "absentLabelsPreference": [ "cat" ] }`), }, }, { Name: "ServiceAffinity", Args: runtime.Unknown{ Raw: []byte(`{ affinityLabels: [ "foo", "bar" ], antiAffinityLabelsPreference: [ "disk", "flash" ] }`), }, </ins> }, <del> "BindPlugin": {{Name: "DefaultBinder"}}, </del> }, }, { <del> name: "Disable some default plugins", </del> <ins> name: "disable some default plugins", </ins> plugins: config.Plugins{ PreFilter: &config.PluginSet{ Disabled: []config.Plugin{

}, }, { <del> name: "Reverse default plugins order with changing score weight", </del> <ins> name: "reverse default plugins order with changing score weight", </ins> plugins: config.Plugins{ QueueSort: &config.PluginSet{ Enabled: []config.Plugin{

scheduler.WithProfiles(config.KubeSchedulerProfile{ SchedulerName: v1.DefaultSchedulerName, Plugins:       &tc.plugins, <ins> PluginConfig:  tc.pluginConfig, }), scheduler.WithBuildFrameworkCapturer(func(p config.KubeSchedulerProfile) { if p.SchedulerName != v1.DefaultSchedulerName { t.Errorf("unexpected scheduler name (want %q, got %q)", v1.DefaultSchedulerName, p.SchedulerName) } if diff := cmp.Diff(tc.pluginConfig, p.PluginConfig); diff != "" { t.Errorf("unexpected plugins diff (-want, +got): %s", diff) } </ins> }), )

t.Fatalf("Error constructing: %v", err) } <del> defProf := sched.Profiles["default-scheduler"] </del> <ins> defProf := sched.Profiles[v1.DefaultSchedulerName] </ins> gotPlugins := defProf.ListPlugins() if diff := cmp.Diff(tc.wantPlugins, gotPlugins); diff != "" { t.Errorf("unexpected plugins diff (-want, +got): %s", diff)

enableNonPreempting bool <del> profiles         []schedulerapi.KubeSchedulerProfile registry         framework.Registry nodeInfoSnapshot *internalcache.Snapshot extenders        []schedulerapi.Extender </del> <ins> profiles          []schedulerapi.KubeSchedulerProfile registry          framework.Registry nodeInfoSnapshot  *internalcache.Snapshot extenders         []schedulerapi.Extender frameworkCapturer FrameworkCapturer </ins> } func (c *Configurator) buildFramework(p schedulerapi.KubeSchedulerProfile) (framework.Framework, error) { <ins> if c.frameworkCapturer != nil { c.frameworkCapturer(p) } </ins> return framework.NewFramework( c.registry, p.Plugins,

} // New initializes a new plugin and returns it. <del> func New(plArgs *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) { </del> <ins> func New(_ *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) { </ins> return &PrioritySort{}, nil }

frameworkOutOfTreeRegistry framework.Registry profiles                   []schedulerapi.KubeSchedulerProfile extenders                  []schedulerapi.Extender <ins> frameworkCapturer          FrameworkCapturer </ins> } // Option configures a Scheduler

} } <ins> // FrameworkCapturer is used for registering a notify function in building framework. type FrameworkCapturer func(schedulerapi.KubeSchedulerProfile) // WithBuildFrameworkCapturer sets a notify function for getting buildFramework details. func WithBuildFrameworkCapturer(fc FrameworkCapturer) Option { return func(o *schedulerOptions) { o.frameworkCapturer = fc } } </ins> var defaultSchedulerOptions = schedulerOptions{ profiles: []schedulerapi.KubeSchedulerProfile{ // Profiles' default plugins are set from the algorithm provider.

registry:                 registry, nodeInfoSnapshot:         snapshot, extenders:                options.extenders, <ins> frameworkCapturer:        options.frameworkCapturer, </ins> } metrics.Register()