mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-30 17:58:14 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1801 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1801 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2019 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 v1alpha1
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| 	dto "github.com/prometheus/client_model/go"
 | |
| 	v1 "k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/types"
 | |
| 	"k8s.io/kubernetes/pkg/scheduler/apis/config"
 | |
| 	"k8s.io/kubernetes/pkg/scheduler/metrics"
 | |
| 	schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	queueSortPlugin                   = "no-op-queue-sort-plugin"
 | |
| 	scoreWithNormalizePlugin1         = "score-with-normalize-plugin-1"
 | |
| 	scoreWithNormalizePlugin2         = "score-with-normalize-plugin-2"
 | |
| 	scorePlugin1                      = "score-plugin-1"
 | |
| 	pluginNotImplementingScore        = "plugin-not-implementing-score"
 | |
| 	preFilterPluginName               = "prefilter-plugin"
 | |
| 	preFilterWithExtensionsPluginName = "prefilter-with-extensions-plugin"
 | |
| 	duplicatePluginName               = "duplicate-plugin"
 | |
| 	testPlugin                        = "test-plugin"
 | |
| 	permitPlugin                      = "permit-plugin"
 | |
| 	bindPlugin                        = "bind-plugin"
 | |
| )
 | |
| 
 | |
| // TestScoreWithNormalizePlugin implements ScoreWithNormalizePlugin interface.
 | |
| // TestScorePlugin only implements ScorePlugin interface.
 | |
| var _ ScorePlugin = &TestScoreWithNormalizePlugin{}
 | |
| var _ ScorePlugin = &TestScorePlugin{}
 | |
| 
 | |
| func newScoreWithNormalizePlugin1(injArgs *runtime.Unknown, f FrameworkHandle) (Plugin, error) {
 | |
| 	var inj injectedResult
 | |
| 	if err := DecodeInto(injArgs, &inj); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &TestScoreWithNormalizePlugin{scoreWithNormalizePlugin1, inj}, nil
 | |
| }
 | |
| 
 | |
| func newScoreWithNormalizePlugin2(injArgs *runtime.Unknown, f FrameworkHandle) (Plugin, error) {
 | |
| 	var inj injectedResult
 | |
| 	if err := DecodeInto(injArgs, &inj); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &TestScoreWithNormalizePlugin{scoreWithNormalizePlugin2, inj}, nil
 | |
| }
 | |
| 
 | |
| func newScorePlugin1(injArgs *runtime.Unknown, f FrameworkHandle) (Plugin, error) {
 | |
| 	var inj injectedResult
 | |
| 	if err := DecodeInto(injArgs, &inj); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &TestScorePlugin{scorePlugin1, inj}, nil
 | |
| }
 | |
| 
 | |
| func newPluginNotImplementingScore(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 	return &PluginNotImplementingScore{}, nil
 | |
| }
 | |
| 
 | |
| type TestScoreWithNormalizePlugin struct {
 | |
| 	name string
 | |
| 	inj  injectedResult
 | |
| }
 | |
| 
 | |
| func (pl *TestScoreWithNormalizePlugin) Name() string {
 | |
| 	return pl.name
 | |
| }
 | |
| 
 | |
| func (pl *TestScoreWithNormalizePlugin) NormalizeScore(ctx context.Context, state *CycleState, pod *v1.Pod, scores NodeScoreList) *Status {
 | |
| 	return injectNormalizeRes(pl.inj, scores)
 | |
| }
 | |
| 
 | |
| func (pl *TestScoreWithNormalizePlugin) Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status) {
 | |
| 	return setScoreRes(pl.inj)
 | |
| }
 | |
| 
 | |
| func (pl *TestScoreWithNormalizePlugin) ScoreExtensions() ScoreExtensions {
 | |
| 	return pl
 | |
| }
 | |
| 
 | |
| // TestScorePlugin only implements ScorePlugin interface.
 | |
| type TestScorePlugin struct {
 | |
| 	name string
 | |
| 	inj  injectedResult
 | |
| }
 | |
| 
 | |
| func (pl *TestScorePlugin) Name() string {
 | |
| 	return pl.name
 | |
| }
 | |
| 
 | |
| func (pl *TestScorePlugin) Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status) {
 | |
| 	return setScoreRes(pl.inj)
 | |
| }
 | |
| 
 | |
| func (pl *TestScorePlugin) ScoreExtensions() ScoreExtensions {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // PluginNotImplementingScore doesn't implement the ScorePlugin interface.
 | |
| type PluginNotImplementingScore struct{}
 | |
| 
 | |
| func (pl *PluginNotImplementingScore) Name() string {
 | |
| 	return pluginNotImplementingScore
 | |
| }
 | |
| 
 | |
| // TestPlugin implements all Plugin interfaces.
 | |
| type TestPlugin struct {
 | |
| 	name string
 | |
| 	inj  injectedResult
 | |
| }
 | |
| 
 | |
| type TestPluginPreFilterExtension struct {
 | |
| 	inj injectedResult
 | |
| }
 | |
| 
 | |
| func (e *TestPluginPreFilterExtension) AddPod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod, podToAdd *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) *Status {
 | |
| 	return NewStatus(Code(e.inj.PreFilterAddPodStatus), "injected status")
 | |
| }
 | |
| func (e *TestPluginPreFilterExtension) RemovePod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod, podToRemove *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) *Status {
 | |
| 	return NewStatus(Code(e.inj.PreFilterRemovePodStatus), "injected status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) Name() string {
 | |
| 	return pl.name
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status) {
 | |
| 	return 0, NewStatus(Code(pl.inj.ScoreStatus), "injected status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) ScoreExtensions() ScoreExtensions {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status {
 | |
| 	return NewStatus(Code(pl.inj.PreFilterStatus), "injected status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) PreFilterExtensions() PreFilterExtensions {
 | |
| 	return &TestPluginPreFilterExtension{inj: pl.inj}
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) Filter(ctx context.Context, state *CycleState, pod *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) *Status {
 | |
| 	return NewStatus(Code(pl.inj.FilterStatus), "injected filter status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) PostFilter(ctx context.Context, state *CycleState, pod *v1.Pod, nodes []*v1.Node, filteredNodesStatuses NodeToStatusMap) *Status {
 | |
| 	return NewStatus(Code(pl.inj.PostFilterStatus), "injected status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) Reserve(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status {
 | |
| 	return NewStatus(Code(pl.inj.ReserveStatus), "injected status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) PreBind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status {
 | |
| 	return NewStatus(Code(pl.inj.PreBindStatus), "injected status")
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) PostBind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) {}
 | |
| 
 | |
| func (pl *TestPlugin) Unreserve(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) {}
 | |
| 
 | |
| func (pl *TestPlugin) Permit(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (*Status, time.Duration) {
 | |
| 	return NewStatus(Code(pl.inj.PermitStatus), "injected status"), time.Duration(0)
 | |
| }
 | |
| 
 | |
| func (pl *TestPlugin) Bind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status {
 | |
| 	return NewStatus(Code(pl.inj.BindStatus), "injected status")
 | |
| }
 | |
| 
 | |
| // TestPreFilterPlugin only implements PreFilterPlugin interface.
 | |
| type TestPreFilterPlugin struct {
 | |
| 	PreFilterCalled int
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterPlugin) Name() string {
 | |
| 	return preFilterPluginName
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterPlugin) PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status {
 | |
| 	pl.PreFilterCalled++
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterPlugin) PreFilterExtensions() PreFilterExtensions {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // TestPreFilterWithExtensionsPlugin implements Add/Remove interfaces.
 | |
| type TestPreFilterWithExtensionsPlugin struct {
 | |
| 	PreFilterCalled int
 | |
| 	AddCalled       int
 | |
| 	RemoveCalled    int
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterWithExtensionsPlugin) Name() string {
 | |
| 	return preFilterWithExtensionsPluginName
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterWithExtensionsPlugin) PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status {
 | |
| 	pl.PreFilterCalled++
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterWithExtensionsPlugin) AddPod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod,
 | |
| 	podToAdd *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) *Status {
 | |
| 	pl.AddCalled++
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterWithExtensionsPlugin) RemovePod(ctx context.Context, state *CycleState, podToSchedule *v1.Pod,
 | |
| 	podToRemove *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) *Status {
 | |
| 	pl.RemoveCalled++
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (pl *TestPreFilterWithExtensionsPlugin) PreFilterExtensions() PreFilterExtensions {
 | |
| 	return pl
 | |
| }
 | |
| 
 | |
| type TestDuplicatePlugin struct {
 | |
| }
 | |
| 
 | |
| func (dp *TestDuplicatePlugin) Name() string {
 | |
| 	return duplicatePluginName
 | |
| }
 | |
| 
 | |
| func (dp *TestDuplicatePlugin) PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (dp *TestDuplicatePlugin) PreFilterExtensions() PreFilterExtensions {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var _ PreFilterPlugin = &TestDuplicatePlugin{}
 | |
| 
 | |
| func newDuplicatePlugin(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 	return &TestDuplicatePlugin{}, nil
 | |
| }
 | |
| 
 | |
| // TestPermitPlugin only implements PermitPlugin interface.
 | |
| type TestPermitPlugin struct {
 | |
| 	PreFilterCalled int
 | |
| }
 | |
| 
 | |
| func (pp *TestPermitPlugin) Name() string {
 | |
| 	return permitPlugin
 | |
| }
 | |
| func (pp *TestPermitPlugin) Permit(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (*Status, time.Duration) {
 | |
| 	return NewStatus(Wait, ""), time.Duration(10 * time.Second)
 | |
| }
 | |
| 
 | |
| var _ QueueSortPlugin = &TestQueueSortPlugin{}
 | |
| 
 | |
| func newQueueSortPlugin(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 	return &TestQueueSortPlugin{}, nil
 | |
| }
 | |
| 
 | |
| // TestQueueSortPlugin is a no-op implementation for QueueSort extension point.
 | |
| type TestQueueSortPlugin struct{}
 | |
| 
 | |
| func (pl *TestQueueSortPlugin) Name() string {
 | |
| 	return queueSortPlugin
 | |
| }
 | |
| 
 | |
| func (pl *TestQueueSortPlugin) Less(_, _ *PodInfo) bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| var _ BindPlugin = &TestBindPlugin{}
 | |
| 
 | |
| func newBindPlugin(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 	return &TestBindPlugin{}, nil
 | |
| }
 | |
| 
 | |
| // TestBindPlugin is a no-op implementation for Bind extension point.
 | |
| type TestBindPlugin struct{}
 | |
| 
 | |
| func (t TestBindPlugin) Name() string {
 | |
| 	return bindPlugin
 | |
| }
 | |
| 
 | |
| func (t TestBindPlugin) Bind(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) *Status {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var registry = func() Registry {
 | |
| 	r := make(Registry)
 | |
| 	r.Register(scoreWithNormalizePlugin1, newScoreWithNormalizePlugin1)
 | |
| 	r.Register(scoreWithNormalizePlugin2, newScoreWithNormalizePlugin2)
 | |
| 	r.Register(scorePlugin1, newScorePlugin1)
 | |
| 	r.Register(pluginNotImplementingScore, newPluginNotImplementingScore)
 | |
| 	r.Register(duplicatePluginName, newDuplicatePlugin)
 | |
| 	return r
 | |
| }()
 | |
| 
 | |
| var defaultWeights = map[string]int32{
 | |
| 	scoreWithNormalizePlugin1: 1,
 | |
| 	scoreWithNormalizePlugin2: 2,
 | |
| 	scorePlugin1:              1,
 | |
| }
 | |
| 
 | |
| var emptyArgs = make([]config.PluginConfig, 0)
 | |
| var state = &CycleState{}
 | |
| 
 | |
| // Pod is only used for logging errors.
 | |
| var pod = &v1.Pod{}
 | |
| var nodes = []*v1.Node{
 | |
| 	{ObjectMeta: metav1.ObjectMeta{Name: "node1"}},
 | |
| 	{ObjectMeta: metav1.ObjectMeta{Name: "node2"}},
 | |
| }
 | |
| 
 | |
| func newFrameworkWithQueueSortAndBind(r Registry, pl *config.Plugins, plc []config.PluginConfig, opts ...Option) (Framework, error) {
 | |
| 	if _, ok := r[queueSortPlugin]; !ok {
 | |
| 		r[queueSortPlugin] = newQueueSortPlugin
 | |
| 	}
 | |
| 	if _, ok := r[bindPlugin]; !ok {
 | |
| 		r[bindPlugin] = newBindPlugin
 | |
| 	}
 | |
| 	plugins := &config.Plugins{}
 | |
| 	plugins.Append(pl)
 | |
| 	if plugins.QueueSort == nil || len(plugins.QueueSort.Enabled) == 0 {
 | |
| 		plugins.Append(&config.Plugins{
 | |
| 			QueueSort: &config.PluginSet{
 | |
| 				Enabled: []config.Plugin{{Name: queueSortPlugin}},
 | |
| 			},
 | |
| 		})
 | |
| 	}
 | |
| 	if plugins.Bind == nil || len(plugins.Bind.Enabled) == 0 {
 | |
| 		plugins.Append(&config.Plugins{
 | |
| 			Bind: &config.PluginSet{
 | |
| 				Enabled: []config.Plugin{{Name: bindPlugin}},
 | |
| 			},
 | |
| 		})
 | |
| 	}
 | |
| 	return NewFramework(r, plugins, plc, opts...)
 | |
| }
 | |
| 
 | |
| func TestInitFrameworkWithScorePlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name    string
 | |
| 		plugins *config.Plugins
 | |
| 		// If initErr is true, we expect framework initialization to fail.
 | |
| 		initErr bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "enabled Score plugin doesn't exist in registry",
 | |
| 			plugins: buildScoreConfigDefaultWeights("notExist"),
 | |
| 			initErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "enabled Score plugin doesn't extend the ScorePlugin interface",
 | |
| 			plugins: buildScoreConfigDefaultWeights(pluginNotImplementingScore),
 | |
| 			initErr: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "Score plugins are nil",
 | |
| 			plugins: &config.Plugins{Score: nil},
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "enabled Score plugin list is empty",
 | |
| 			plugins: buildScoreConfigDefaultWeights(),
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "enabled plugin only implements ScorePlugin interface",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1),
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "enabled plugin implements ScoreWithNormalizePlugin interface",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			_, err := newFrameworkWithQueueSortAndBind(registry, tt.plugins, emptyArgs)
 | |
| 			if tt.initErr && err == nil {
 | |
| 				t.Fatal("Framework initialization should fail")
 | |
| 			}
 | |
| 			if !tt.initErr && err != nil {
 | |
| 				t.Fatalf("Failed to create framework for testing: %v", err)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestRegisterDuplicatePluginWouldFail(t *testing.T) {
 | |
| 	plugin := config.Plugin{Name: duplicatePluginName, Weight: 1}
 | |
| 
 | |
| 	pluginSet := config.PluginSet{
 | |
| 		Enabled: []config.Plugin{
 | |
| 			plugin,
 | |
| 			plugin,
 | |
| 		},
 | |
| 	}
 | |
| 	plugins := config.Plugins{}
 | |
| 	plugins.PreFilter = &pluginSet
 | |
| 
 | |
| 	_, err := NewFramework(registry, &plugins, emptyArgs)
 | |
| 	if err == nil {
 | |
| 		t.Fatal("Framework initialization should fail")
 | |
| 	}
 | |
| 
 | |
| 	if err != nil && !strings.Contains(err.Error(), "already registered") {
 | |
| 		t.Fatalf("Unexpected error, got %s, expect: plugin already registered", err.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestRunScorePlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		registry      Registry
 | |
| 		plugins       *config.Plugins
 | |
| 		pluginConfigs []config.PluginConfig
 | |
| 		want          PluginToNodeScores
 | |
| 		// If err is true, we expect RunScorePlugin to fail.
 | |
| 		err bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "no Score plugins",
 | |
| 			plugins: buildScoreConfigDefaultWeights(),
 | |
| 			want:    PluginToNodeScores{},
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "single Score plugin",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scorePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "scoreRes": 1 }`),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			// scorePlugin1 Score returns 1, weight=1, so want=1.
 | |
| 			want: PluginToNodeScores{
 | |
| 				scorePlugin1: {{Name: "node1", Score: 1}, {Name: "node2", Score: 1}},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "single ScoreWithNormalize plugin",
 | |
| 			//registry: registry,
 | |
| 			plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "scoreRes": 10, "normalizeRes": 5 }`),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			// scoreWithNormalizePlugin1 Score returns 10, but NormalizeScore overrides to 5, weight=1, so want=5
 | |
| 			want: PluginToNodeScores{
 | |
| 				scoreWithNormalizePlugin1: {{Name: "node1", Score: 5}, {Name: "node2", Score: 5}},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "2 Score plugins, 2 NormalizeScore plugins",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1, scoreWithNormalizePlugin2),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scorePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "scoreRes": 1 }`),
 | |
| 					},
 | |
| 				},
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "scoreRes": 3, "normalizeRes": 4}`),
 | |
| 					},
 | |
| 				},
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin2,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "scoreRes": 4, "normalizeRes": 5}`),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			// scorePlugin1 Score returns 1, weight =1, so want=1.
 | |
| 			// scoreWithNormalizePlugin1 Score returns 3, but NormalizeScore overrides to 4, weight=1, so want=4.
 | |
| 			// scoreWithNormalizePlugin2 Score returns 4, but NormalizeScore overrides to 5, weight=2, so want=10.
 | |
| 			want: PluginToNodeScores{
 | |
| 				scorePlugin1:              {{Name: "node1", Score: 1}, {Name: "node2", Score: 1}},
 | |
| 				scoreWithNormalizePlugin1: {{Name: "node1", Score: 4}, {Name: "node2", Score: 4}},
 | |
| 				scoreWithNormalizePlugin2: {{Name: "node1", Score: 10}, {Name: "node2", Score: 10}},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "score fails",
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "scoreStatus": 1 }`),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1),
 | |
| 			err:     true,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "normalize fails",
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(`{ "normalizeStatus": 1 }`),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1),
 | |
| 			err:     true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "Score plugin return score greater than MaxNodeScore",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scorePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(fmt.Sprintf(`{ "scoreRes": %d }`, MaxNodeScore+1)),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			err: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "Score plugin return score less than MinNodeScore",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scorePlugin1),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scorePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(fmt.Sprintf(`{ "scoreRes": %d }`, MinNodeScore-1)),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			err: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "ScoreWithNormalize plugin return score greater than MaxNodeScore",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(fmt.Sprintf(`{ "normalizeRes": %d }`, MaxNodeScore+1)),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			err: true,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "ScoreWithNormalize plugin return score less than MinNodeScore",
 | |
| 			plugins: buildScoreConfigDefaultWeights(scoreWithNormalizePlugin1),
 | |
| 			pluginConfigs: []config.PluginConfig{
 | |
| 				{
 | |
| 					Name: scoreWithNormalizePlugin1,
 | |
| 					Args: runtime.Unknown{
 | |
| 						Raw: []byte(fmt.Sprintf(`{ "normalizeRes": %d }`, MinNodeScore-1)),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			err: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			// Inject the results via Args in PluginConfig.
 | |
| 			f, err := newFrameworkWithQueueSortAndBind(registry, tt.plugins, tt.pluginConfigs)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("Failed to create framework for testing: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			res, status := f.RunScorePlugins(context.Background(), state, pod, nodes)
 | |
| 
 | |
| 			if tt.err {
 | |
| 				if status.IsSuccess() {
 | |
| 					t.Errorf("Expected status to be non-success. got: %v", status.Code().String())
 | |
| 				}
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			if !status.IsSuccess() {
 | |
| 				t.Errorf("Expected status to be success.")
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(res, tt.want) {
 | |
| 				t.Errorf("Score map after RunScorePlugin: %+v, want: %+v.", res, tt.want)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPreFilterPlugins(t *testing.T) {
 | |
| 	preFilter1 := &TestPreFilterPlugin{}
 | |
| 	preFilter2 := &TestPreFilterWithExtensionsPlugin{}
 | |
| 	r := make(Registry)
 | |
| 	r.Register(preFilterPluginName,
 | |
| 		func(_ *runtime.Unknown, fh FrameworkHandle) (Plugin, error) {
 | |
| 			return preFilter1, nil
 | |
| 		})
 | |
| 	r.Register(preFilterWithExtensionsPluginName,
 | |
| 		func(_ *runtime.Unknown, fh FrameworkHandle) (Plugin, error) {
 | |
| 			return preFilter2, nil
 | |
| 		})
 | |
| 	plugins := &config.Plugins{PreFilter: &config.PluginSet{Enabled: []config.Plugin{{Name: preFilterWithExtensionsPluginName}, {Name: preFilterPluginName}}}}
 | |
| 	t.Run("TestPreFilterPlugin", func(t *testing.T) {
 | |
| 		f, err := newFrameworkWithQueueSortAndBind(r, plugins, emptyArgs)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Failed to create framework for testing: %v", err)
 | |
| 		}
 | |
| 		f.RunPreFilterPlugins(context.Background(), nil, nil)
 | |
| 		f.RunPreFilterExtensionAddPod(context.Background(), nil, nil, nil, nil)
 | |
| 		f.RunPreFilterExtensionRemovePod(context.Background(), nil, nil, nil, nil)
 | |
| 
 | |
| 		if preFilter1.PreFilterCalled != 1 {
 | |
| 			t.Errorf("preFilter1 called %v, expected: 1", preFilter1.PreFilterCalled)
 | |
| 		}
 | |
| 		if preFilter2.PreFilterCalled != 1 {
 | |
| 			t.Errorf("preFilter2 called %v, expected: 1", preFilter2.PreFilterCalled)
 | |
| 		}
 | |
| 		if preFilter2.AddCalled != 1 {
 | |
| 			t.Errorf("AddPod called %v, expected: 1", preFilter2.AddCalled)
 | |
| 		}
 | |
| 		if preFilter2.RemoveCalled != 1 {
 | |
| 			t.Errorf("AddPod called %v, expected: 1", preFilter2.RemoveCalled)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestFilterPlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		plugins       []*TestPlugin
 | |
| 		wantStatus    *Status
 | |
| 		wantStatusMap PluginToStatus
 | |
| 		runAllFilters bool
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "SuccessFilter",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    nil,
 | |
| 			wantStatusMap: PluginToStatus{},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorFilter",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(Error, `running "TestPlugin" filter plugin for pod "": injected filter status`),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin": NewStatus(Error, `running "TestPlugin" filter plugin for pod "": injected filter status`)},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulableFilter",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{FilterStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(Unschedulable, "injected filter status"),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin": NewStatus(Unschedulable, "injected filter status")},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulableAndUnresolvableFilter",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj: injectedResult{
 | |
| 						FilterStatus: int(UnschedulableAndUnresolvable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(UnschedulableAndUnresolvable, "injected filter status"),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin": NewStatus(UnschedulableAndUnresolvable, "injected filter status")},
 | |
| 		},
 | |
| 		// followings tests cover multiple-plugins scenarios
 | |
| 		{
 | |
| 			name: "ErrorAndErrorFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(Error, `running "TestPlugin1" filter plugin for pod "": injected filter status`),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin1": NewStatus(Error, `running "TestPlugin1" filter plugin for pod "": injected filter status`)},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessAndSuccessFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    nil,
 | |
| 			wantStatusMap: PluginToStatus{},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorAndSuccessFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(Error, `running "TestPlugin1" filter plugin for pod "": injected filter status`),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin1": NewStatus(Error, `running "TestPlugin1" filter plugin for pod "": injected filter status`)},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessAndErrorFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(Error, `running "TestPlugin2" filter plugin for pod "": injected filter status`),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin2": NewStatus(Error, `running "TestPlugin2" filter plugin for pod "": injected filter status`)},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessAndUnschedulableFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus:    NewStatus(Unschedulable, "injected filter status"),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin2": NewStatus(Unschedulable, "injected filter status")},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessFilterWithRunAllFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{FilterStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			runAllFilters: true,
 | |
| 			wantStatus:    nil,
 | |
| 			wantStatusMap: PluginToStatus{},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorAndErrorFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			runAllFilters: true,
 | |
| 			wantStatus:    NewStatus(Error, `running "TestPlugin1" filter plugin for pod "": injected filter status`),
 | |
| 			wantStatusMap: PluginToStatus{"TestPlugin1": NewStatus(Error, `running "TestPlugin1" filter plugin for pod "": injected filter status`)},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorAndErrorFilters",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin1",
 | |
| 					inj:  injectedResult{FilterStatus: int(UnschedulableAndUnresolvable)},
 | |
| 				},
 | |
| 
 | |
| 				{
 | |
| 					name: "TestPlugin2",
 | |
| 					inj:  injectedResult{FilterStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 			},
 | |
| 			runAllFilters: true,
 | |
| 			wantStatus:    NewStatus(UnschedulableAndUnresolvable, "injected filter status", "injected filter status"),
 | |
| 			wantStatusMap: PluginToStatus{
 | |
| 				"TestPlugin1": NewStatus(UnschedulableAndUnresolvable, "injected filter status"),
 | |
| 				"TestPlugin2": NewStatus(Unschedulable, "injected filter status"),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			registry := Registry{}
 | |
| 			cfgPls := &config.Plugins{Filter: &config.PluginSet{}}
 | |
| 			for _, pl := range tt.plugins {
 | |
| 				// register all plugins
 | |
| 				tmpPl := pl
 | |
| 				if err := registry.Register(pl.name,
 | |
| 					func(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 						return tmpPl, nil
 | |
| 					}); err != nil {
 | |
| 					t.Fatalf("fail to register filter plugin (%s)", pl.name)
 | |
| 				}
 | |
| 				// append plugins to filter pluginset
 | |
| 				cfgPls.Filter.Enabled = append(
 | |
| 					cfgPls.Filter.Enabled,
 | |
| 					config.Plugin{Name: pl.name})
 | |
| 			}
 | |
| 
 | |
| 			f, err := newFrameworkWithQueueSortAndBind(registry, cfgPls, emptyArgs, WithRunAllFilters(tt.runAllFilters))
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("fail to create framework: %s", err)
 | |
| 			}
 | |
| 			gotStatusMap := f.RunFilterPlugins(context.TODO(), nil, pod, nil)
 | |
| 			gotStatus := gotStatusMap.Merge()
 | |
| 			if !reflect.DeepEqual(gotStatus, tt.wantStatus) {
 | |
| 				t.Errorf("wrong status code. got: %v, want:%v", gotStatus, tt.wantStatus)
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(gotStatusMap, tt.wantStatusMap) {
 | |
| 				t.Errorf("wrong status map. got: %+v, want: %+v", gotStatusMap, tt.wantStatusMap)
 | |
| 			}
 | |
| 
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPreBindPlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name       string
 | |
| 		plugins    []*TestPlugin
 | |
| 		wantStatus *Status
 | |
| 	}{
 | |
| 		{
 | |
| 			name:       "NoPreBindPlugin",
 | |
| 			plugins:    []*TestPlugin{},
 | |
| 			wantStatus: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessPreBindPlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnshedulablePreBindPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorPreBindPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulablePreBindPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(UnschedulableAndUnresolvable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessErrorPreBindPlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Success)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin 1" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorSuccessPreBindPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Error)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessSuccessPreBindPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Success)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorAndErrorPlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Error)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulableAndSuccessPreBindPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PreBindStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" prebind plugin for pod "": injected status`),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			registry := Registry{}
 | |
| 			configPlugins := &config.Plugins{PreBind: &config.PluginSet{}}
 | |
| 
 | |
| 			for _, pl := range tt.plugins {
 | |
| 				tmpPl := pl
 | |
| 				if err := registry.Register(pl.name, func(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 					return tmpPl, nil
 | |
| 				}); err != nil {
 | |
| 					t.Fatalf("Unable to register pre bind plugins: %s", pl.name)
 | |
| 				}
 | |
| 
 | |
| 				configPlugins.PreBind.Enabled = append(
 | |
| 					configPlugins.PreBind.Enabled,
 | |
| 					config.Plugin{Name: pl.name},
 | |
| 				)
 | |
| 			}
 | |
| 
 | |
| 			f, err := newFrameworkWithQueueSortAndBind(registry, configPlugins, emptyArgs)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("fail to create framework: %s", err)
 | |
| 			}
 | |
| 
 | |
| 			status := f.RunPreBindPlugins(context.TODO(), nil, pod, "")
 | |
| 
 | |
| 			if !reflect.DeepEqual(status, tt.wantStatus) {
 | |
| 				t.Errorf("wrong status code. got %v, want %v", status, tt.wantStatus)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReservePlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name       string
 | |
| 		plugins    []*TestPlugin
 | |
| 		wantStatus *Status
 | |
| 	}{
 | |
| 		{
 | |
| 			name:       "NoReservePlugin",
 | |
| 			plugins:    []*TestPlugin{},
 | |
| 			wantStatus: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessReservePlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnshedulableReservePlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorReservePlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulableReservePlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(UnschedulableAndUnresolvable)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessSuccessReservePlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Success)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorErrorReservePlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Error)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessErrorReservePlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Success)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin 1" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorSuccessReservePlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Error)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulableAndSuccessReservePlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{ReserveStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			wantStatus: NewStatus(Error, `error while running "TestPlugin" reserve plugin for pod "": injected status`),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			registry := Registry{}
 | |
| 			configPlugins := &config.Plugins{Reserve: &config.PluginSet{}}
 | |
| 
 | |
| 			for _, pl := range tt.plugins {
 | |
| 				tmpPl := pl
 | |
| 				if err := registry.Register(pl.name, func(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 					return tmpPl, nil
 | |
| 				}); err != nil {
 | |
| 					t.Fatalf("Unable to register pre bind plugins: %s", pl.name)
 | |
| 				}
 | |
| 
 | |
| 				configPlugins.Reserve.Enabled = append(
 | |
| 					configPlugins.Reserve.Enabled,
 | |
| 					config.Plugin{Name: pl.name},
 | |
| 				)
 | |
| 			}
 | |
| 
 | |
| 			f, err := newFrameworkWithQueueSortAndBind(registry, configPlugins, emptyArgs)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("fail to create framework: %s", err)
 | |
| 			}
 | |
| 
 | |
| 			status := f.RunReservePlugins(context.TODO(), nil, pod, "")
 | |
| 
 | |
| 			if !reflect.DeepEqual(status, tt.wantStatus) {
 | |
| 				t.Errorf("wrong status code. got %v, want %v", status, tt.wantStatus)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPermitPlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name    string
 | |
| 		plugins []*TestPlugin
 | |
| 		want    *Status
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "NilPermitPlugin",
 | |
| 			plugins: []*TestPlugin{},
 | |
| 			want:    nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessPermitPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulablePermitPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(Unschedulable)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: NewStatus(Unschedulable, `rejected by "TestPlugin" at permit: injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorPermitPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: NewStatus(Error, `error while running "TestPlugin" permit plugin for pod "": injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "UnschedulableAndUnresolvablePermitPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(UnschedulableAndUnresolvable)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: NewStatus(UnschedulableAndUnresolvable, `rejected by "TestPlugin" at permit: injected status`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "WaitPermitPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(Wait)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: NewStatus(Unschedulable, `pod "" rejected while waiting at permit: rejected due to timeout after waiting 0s at plugin TestPlugin`),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "SuccessSuccessPermitPlugin",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(Success)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PermitStatus: int(Success)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "ErrorAndErrorPlugins",
 | |
| 			plugins: []*TestPlugin{
 | |
| 				{
 | |
| 					name: "TestPlugin",
 | |
| 					inj:  injectedResult{PermitStatus: int(Error)},
 | |
| 				},
 | |
| 				{
 | |
| 					name: "TestPlugin 1",
 | |
| 					inj:  injectedResult{PermitStatus: int(Error)},
 | |
| 				},
 | |
| 			},
 | |
| 			want: NewStatus(Error, `error while running "TestPlugin" permit plugin for pod "": injected status`),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		registry := Registry{}
 | |
| 		configPlugins := &config.Plugins{Permit: &config.PluginSet{}}
 | |
| 
 | |
| 		for _, pl := range tt.plugins {
 | |
| 			tmpPl := pl
 | |
| 			if err := registry.Register(pl.name, func(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
 | |
| 				return tmpPl, nil
 | |
| 			}); err != nil {
 | |
| 				t.Fatalf("Unable to register Permit plugin: %s", pl.name)
 | |
| 			}
 | |
| 
 | |
| 			configPlugins.Permit.Enabled = append(
 | |
| 				configPlugins.Permit.Enabled,
 | |
| 				config.Plugin{Name: pl.name},
 | |
| 			)
 | |
| 		}
 | |
| 
 | |
| 		f, err := newFrameworkWithQueueSortAndBind(registry, configPlugins, emptyArgs)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("fail to create framework: %s", err)
 | |
| 		}
 | |
| 
 | |
| 		status := f.RunPermitPlugins(context.TODO(), nil, pod, "")
 | |
| 
 | |
| 		if !reflect.DeepEqual(status, tt.want) {
 | |
| 			t.Errorf("wrong status code. got %v, want %v", status, tt.want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestRecordingMetrics(t *testing.T) {
 | |
| 	state := &CycleState{
 | |
| 		recordPluginMetrics: true,
 | |
| 	}
 | |
| 	tests := []struct {
 | |
| 		name               string
 | |
| 		action             func(f Framework)
 | |
| 		inject             injectedResult
 | |
| 		wantExtensionPoint string
 | |
| 		wantStatus         Code
 | |
| 	}{
 | |
| 		{
 | |
| 			name:               "PreFilter - Success",
 | |
| 			action:             func(f Framework) { f.RunPreFilterPlugins(context.Background(), state, pod) },
 | |
| 			wantExtensionPoint: "PreFilter",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "PostFilter - Success",
 | |
| 			action:             func(f Framework) { f.RunPostFilterPlugins(context.Background(), state, pod, nil, nil) },
 | |
| 			wantExtensionPoint: "PostFilter",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Score - Success",
 | |
| 			action:             func(f Framework) { f.RunScorePlugins(context.Background(), state, pod, nodes) },
 | |
| 			wantExtensionPoint: "Score",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Reserve - Success",
 | |
| 			action:             func(f Framework) { f.RunReservePlugins(context.Background(), state, pod, "") },
 | |
| 			wantExtensionPoint: "Reserve",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Unreserve - Success",
 | |
| 			action:             func(f Framework) { f.RunUnreservePlugins(context.Background(), state, pod, "") },
 | |
| 			wantExtensionPoint: "Unreserve",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "PreBind - Success",
 | |
| 			action:             func(f Framework) { f.RunPreBindPlugins(context.Background(), state, pod, "") },
 | |
| 			wantExtensionPoint: "PreBind",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Bind - Success",
 | |
| 			action:             func(f Framework) { f.RunBindPlugins(context.Background(), state, pod, "") },
 | |
| 			wantExtensionPoint: "Bind",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "PostBind - Success",
 | |
| 			action:             func(f Framework) { f.RunPostBindPlugins(context.Background(), state, pod, "") },
 | |
| 			wantExtensionPoint: "PostBind",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Permit - Success",
 | |
| 			action:             func(f Framework) { f.RunPermitPlugins(context.Background(), state, pod, "") },
 | |
| 			wantExtensionPoint: "Permit",
 | |
| 			wantStatus:         Success,
 | |
| 		},
 | |
| 
 | |
| 		{
 | |
| 			name:               "PreFilter - Error",
 | |
| 			action:             func(f Framework) { f.RunPreFilterPlugins(context.Background(), state, pod) },
 | |
| 			inject:             injectedResult{PreFilterStatus: int(Error)},
 | |
| 			wantExtensionPoint: "PreFilter",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "PostFilter - Error",
 | |
| 			action:             func(f Framework) { f.RunPostFilterPlugins(context.Background(), state, pod, nil, nil) },
 | |
| 			inject:             injectedResult{PostFilterStatus: int(Error)},
 | |
| 			wantExtensionPoint: "PostFilter",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Score - Error",
 | |
| 			action:             func(f Framework) { f.RunScorePlugins(context.Background(), state, pod, nodes) },
 | |
| 			inject:             injectedResult{ScoreStatus: int(Error)},
 | |
| 			wantExtensionPoint: "Score",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Reserve - Error",
 | |
| 			action:             func(f Framework) { f.RunReservePlugins(context.Background(), state, pod, "") },
 | |
| 			inject:             injectedResult{ReserveStatus: int(Error)},
 | |
| 			wantExtensionPoint: "Reserve",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "PreBind - Error",
 | |
| 			action:             func(f Framework) { f.RunPreBindPlugins(context.Background(), state, pod, "") },
 | |
| 			inject:             injectedResult{PreBindStatus: int(Error)},
 | |
| 			wantExtensionPoint: "PreBind",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Bind - Error",
 | |
| 			action:             func(f Framework) { f.RunBindPlugins(context.Background(), state, pod, "") },
 | |
| 			inject:             injectedResult{BindStatus: int(Error)},
 | |
| 			wantExtensionPoint: "Bind",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "Permit - Error",
 | |
| 			action:             func(f Framework) { f.RunPermitPlugins(context.Background(), state, pod, "") },
 | |
| 			inject:             injectedResult{PermitStatus: int(Error)},
 | |
| 			wantExtensionPoint: "Permit",
 | |
| 			wantStatus:         Error,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			metrics.Register()
 | |
| 			metrics.FrameworkExtensionPointDuration.Reset()
 | |
| 			metrics.PluginExecutionDuration.Reset()
 | |
| 
 | |
| 			plugin := &TestPlugin{name: testPlugin, inj: tt.inject}
 | |
| 			r := make(Registry)
 | |
| 			r.Register(testPlugin,
 | |
| 				func(_ *runtime.Unknown, fh FrameworkHandle) (Plugin, error) {
 | |
| 					return plugin, nil
 | |
| 				})
 | |
| 			pluginSet := &config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}}
 | |
| 			plugins := &config.Plugins{
 | |
| 				Score:      pluginSet,
 | |
| 				PreFilter:  pluginSet,
 | |
| 				Filter:     pluginSet,
 | |
| 				PostFilter: pluginSet,
 | |
| 				Reserve:    pluginSet,
 | |
| 				Permit:     pluginSet,
 | |
| 				PreBind:    pluginSet,
 | |
| 				Bind:       pluginSet,
 | |
| 				PostBind:   pluginSet,
 | |
| 				Unreserve:  pluginSet,
 | |
| 			}
 | |
| 			recorder := newMetricsRecorder(100, time.Nanosecond)
 | |
| 			f, err := newFrameworkWithQueueSortAndBind(r, plugins, emptyArgs, withMetricsRecorder(recorder))
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("Failed to create framework for testing: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			tt.action(f)
 | |
| 
 | |
| 			// Stop the goroutine which records metrics and ensure it's stopped.
 | |
| 			close(recorder.stopCh)
 | |
| 			<-recorder.isStoppedCh
 | |
| 			// Try to clean up the metrics buffer again in case it's not empty.
 | |
| 			recorder.flushMetrics()
 | |
| 
 | |
| 			collectAndCompareFrameworkMetrics(t, tt.wantExtensionPoint, tt.wantStatus)
 | |
| 			collectAndComparePluginMetrics(t, tt.wantExtensionPoint, testPlugin, tt.wantStatus)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestRunBindPlugins(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name       string
 | |
| 		injects    []Code
 | |
| 		wantStatus Code
 | |
| 	}{
 | |
| 		{
 | |
| 			name:       "simple success",
 | |
| 			injects:    []Code{Success},
 | |
| 			wantStatus: Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "error on second",
 | |
| 			injects:    []Code{Skip, Error, Success},
 | |
| 			wantStatus: Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "all skip",
 | |
| 			injects:    []Code{Skip, Skip, Skip},
 | |
| 			wantStatus: Skip,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "error on third, but not reached",
 | |
| 			injects:    []Code{Skip, Success, Error},
 | |
| 			wantStatus: Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "no bind plugin, returns default binder",
 | |
| 			injects:    []Code{},
 | |
| 			wantStatus: Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "invalid status",
 | |
| 			injects:    []Code{Unschedulable},
 | |
| 			wantStatus: Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "simple error",
 | |
| 			injects:    []Code{Error},
 | |
| 			wantStatus: Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "success on second, returns success",
 | |
| 			injects:    []Code{Skip, Success},
 | |
| 			wantStatus: Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "invalid status, returns error",
 | |
| 			injects:    []Code{Skip, UnschedulableAndUnresolvable},
 | |
| 			wantStatus: Error,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "error after success status, returns success",
 | |
| 			injects:    []Code{Success, Error},
 | |
| 			wantStatus: Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "success before invalid status, returns success",
 | |
| 			injects:    []Code{Success, Error},
 | |
| 			wantStatus: Success,
 | |
| 		},
 | |
| 		{
 | |
| 			name:       "success after error status, returns error",
 | |
| 			injects:    []Code{Error, Success},
 | |
| 			wantStatus: Error,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			metrics.Register()
 | |
| 			metrics.FrameworkExtensionPointDuration.Reset()
 | |
| 			metrics.PluginExecutionDuration.Reset()
 | |
| 
 | |
| 			pluginSet := &config.PluginSet{}
 | |
| 			r := make(Registry)
 | |
| 			for i, inj := range tt.injects {
 | |
| 				name := fmt.Sprintf("bind-%d", i)
 | |
| 				plugin := &TestPlugin{name: name, inj: injectedResult{BindStatus: int(inj)}}
 | |
| 				r.Register(name,
 | |
| 					func(_ *runtime.Unknown, fh FrameworkHandle) (Plugin, error) {
 | |
| 						return plugin, nil
 | |
| 					})
 | |
| 				pluginSet.Enabled = append(pluginSet.Enabled, config.Plugin{Name: name})
 | |
| 			}
 | |
| 			plugins := &config.Plugins{Bind: pluginSet}
 | |
| 			recorder := newMetricsRecorder(100, time.Nanosecond)
 | |
| 			fwk, err := newFrameworkWithQueueSortAndBind(r, plugins, emptyArgs, withMetricsRecorder(recorder))
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 
 | |
| 			st := fwk.RunBindPlugins(context.Background(), state, pod, "")
 | |
| 			if st.Code() != tt.wantStatus {
 | |
| 				t.Errorf("got status code %s, want %s", st.Code(), tt.wantStatus)
 | |
| 			}
 | |
| 
 | |
| 			// Stop the goroutine which records metrics and ensure it's stopped.
 | |
| 			close(recorder.stopCh)
 | |
| 			<-recorder.isStoppedCh
 | |
| 			// Try to clean up the metrics buffer again in case it's not empty.
 | |
| 			recorder.flushMetrics()
 | |
| 			collectAndCompareFrameworkMetrics(t, "Bind", tt.wantStatus)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestPermitWaitingMetric(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		name    string
 | |
| 		inject  injectedResult
 | |
| 		wantRes string
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "Permit - Success",
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "Permit - Wait Timeout",
 | |
| 			inject:  injectedResult{PermitStatus: int(Wait)},
 | |
| 			wantRes: "Unschedulable",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			metrics.Register()
 | |
| 			metrics.PermitWaitDuration.Reset()
 | |
| 
 | |
| 			plugin := &TestPlugin{name: testPlugin, inj: tt.inject}
 | |
| 			r := make(Registry)
 | |
| 			err := r.Register(testPlugin,
 | |
| 				func(_ *runtime.Unknown, fh FrameworkHandle) (Plugin, error) {
 | |
| 					return plugin, nil
 | |
| 				})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			plugins := &config.Plugins{
 | |
| 				Permit: &config.PluginSet{Enabled: []config.Plugin{{Name: testPlugin, Weight: 1}}},
 | |
| 			}
 | |
| 			f, err := newFrameworkWithQueueSortAndBind(r, plugins, emptyArgs)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("Failed to create framework for testing: %v", err)
 | |
| 			}
 | |
| 
 | |
| 			f.RunPermitPlugins(context.TODO(), nil, pod, "")
 | |
| 
 | |
| 			collectAndComparePermitWaitDuration(t, tt.wantRes)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestRejectWaitingPod(t *testing.T) {
 | |
| 	pod := &v1.Pod{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			Name: "pod",
 | |
| 			UID:  types.UID("pod"),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	testPermitPlugin := &TestPermitPlugin{}
 | |
| 	r := make(Registry)
 | |
| 	r.Register(permitPlugin,
 | |
| 		func(_ *runtime.Unknown, fh FrameworkHandle) (Plugin, error) {
 | |
| 			return testPermitPlugin, nil
 | |
| 		})
 | |
| 	plugins := &config.Plugins{
 | |
| 		Permit: &config.PluginSet{Enabled: []config.Plugin{{Name: permitPlugin, Weight: 1}}},
 | |
| 	}
 | |
| 
 | |
| 	f, err := newFrameworkWithQueueSortAndBind(r, plugins, emptyArgs)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to create framework for testing: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		for {
 | |
| 			waitingPod := f.GetWaitingPod(pod.UID)
 | |
| 			if waitingPod != nil {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		f.RejectWaitingPod(pod.UID)
 | |
| 	}()
 | |
| 	permitStatus := f.RunPermitPlugins(context.Background(), nil, pod, "")
 | |
| 	if permitStatus.Message() != "pod \"pod\" rejected while waiting at permit: removed" {
 | |
| 		t.Fatalf("RejectWaitingPod failed, permitStatus: %v", permitStatus)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func buildScoreConfigDefaultWeights(ps ...string) *config.Plugins {
 | |
| 	return buildScoreConfigWithWeights(defaultWeights, ps...)
 | |
| }
 | |
| 
 | |
| func buildScoreConfigWithWeights(weights map[string]int32, ps ...string) *config.Plugins {
 | |
| 	var plugins []config.Plugin
 | |
| 	for _, p := range ps {
 | |
| 		plugins = append(plugins, config.Plugin{Name: p, Weight: weights[p]})
 | |
| 	}
 | |
| 	return &config.Plugins{Score: &config.PluginSet{Enabled: plugins}}
 | |
| }
 | |
| 
 | |
| type injectedResult struct {
 | |
| 	ScoreRes                 int64 `json:"scoreRes,omitempty"`
 | |
| 	NormalizeRes             int64 `json:"normalizeRes,omitempty"`
 | |
| 	ScoreStatus              int   `json:"scoreStatus,omitempty"`
 | |
| 	NormalizeStatus          int   `json:"normalizeStatus,omitempty"`
 | |
| 	PreFilterStatus          int   `json:"preFilterStatus,omitempty"`
 | |
| 	PreFilterAddPodStatus    int   `json:"preFilterAddPodStatus,omitempty"`
 | |
| 	PreFilterRemovePodStatus int   `json:"preFilterRemovePodStatus,omitempty"`
 | |
| 	FilterStatus             int   `json:"filterStatus,omitempty"`
 | |
| 	PostFilterStatus         int   `json:"postFilterStatus,omitempty"`
 | |
| 	ReserveStatus            int   `json:"reserveStatus,omitempty"`
 | |
| 	PreBindStatus            int   `json:"preBindStatus,omitempty"`
 | |
| 	BindStatus               int   `json:"bindStatus,omitempty"`
 | |
| 	PermitStatus             int   `json:"permitStatus,omitempty"`
 | |
| }
 | |
| 
 | |
| func setScoreRes(inj injectedResult) (int64, *Status) {
 | |
| 	if Code(inj.ScoreStatus) != Success {
 | |
| 		return 0, NewStatus(Code(inj.ScoreStatus), "injecting failure.")
 | |
| 	}
 | |
| 	return inj.ScoreRes, nil
 | |
| }
 | |
| 
 | |
| func injectNormalizeRes(inj injectedResult, scores NodeScoreList) *Status {
 | |
| 	if Code(inj.NormalizeStatus) != Success {
 | |
| 		return NewStatus(Code(inj.NormalizeStatus), "injecting failure.")
 | |
| 	}
 | |
| 	for i := range scores {
 | |
| 		scores[i].Score = inj.NormalizeRes
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func collectAndComparePluginMetrics(t *testing.T, wantExtensionPoint, wantPlugin string, wantStatus Code) {
 | |
| 	t.Helper()
 | |
| 	m := collectHistogramMetric(metrics.PluginExecutionDuration)
 | |
| 	if len(m.Label) != 3 {
 | |
| 		t.Fatalf("Unexpected number of label pairs, got: %v, want: 2", len(m.Label))
 | |
| 	}
 | |
| 
 | |
| 	if *m.Label[0].Value != wantExtensionPoint {
 | |
| 		t.Errorf("Unexpected extension point label, got: %q, want %q", *m.Label[0].Value, wantExtensionPoint)
 | |
| 	}
 | |
| 
 | |
| 	if *m.Label[1].Value != wantPlugin {
 | |
| 		t.Errorf("Unexpected plugin label, got: %q, want %q", *m.Label[1].Value, wantPlugin)
 | |
| 	}
 | |
| 
 | |
| 	if *m.Label[2].Value != wantStatus.String() {
 | |
| 		t.Errorf("Unexpected status code label, got: %q, want %q", *m.Label[2].Value, wantStatus)
 | |
| 	}
 | |
| 
 | |
| 	if *m.Histogram.SampleCount == 0 {
 | |
| 		t.Error("Expect at least 1 sample")
 | |
| 	}
 | |
| 
 | |
| 	if *m.Histogram.SampleSum <= 0 {
 | |
| 		t.Errorf("Expect latency to be greater than 0, got: %v", *m.Histogram.SampleSum)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func collectAndCompareFrameworkMetrics(t *testing.T, wantExtensionPoint string, wantStatus Code) {
 | |
| 	t.Helper()
 | |
| 	m := collectHistogramMetric(metrics.FrameworkExtensionPointDuration)
 | |
| 
 | |
| 	if len(m.Label) != 2 {
 | |
| 		t.Fatalf("Unexpected number of label pairs, got: %v, want: 2", len(m.Label))
 | |
| 	}
 | |
| 
 | |
| 	if *m.Label[0].Value != wantExtensionPoint {
 | |
| 		t.Errorf("Unexpected extension point label, got: %q, want %q", *m.Label[0].Value, wantExtensionPoint)
 | |
| 	}
 | |
| 
 | |
| 	if *m.Label[1].Value != wantStatus.String() {
 | |
| 		t.Errorf("Unexpected status code label, got: %q, want %q", *m.Label[1].Value, wantStatus)
 | |
| 	}
 | |
| 
 | |
| 	if *m.Histogram.SampleCount != 1 {
 | |
| 		t.Errorf("Expect 1 sample, got: %v", *m.Histogram.SampleCount)
 | |
| 	}
 | |
| 
 | |
| 	if *m.Histogram.SampleSum <= 0 {
 | |
| 		t.Errorf("Expect latency to be greater than 0, got: %v", *m.Histogram.SampleSum)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func collectAndComparePermitWaitDuration(t *testing.T, wantRes string) {
 | |
| 	m := collectHistogramMetric(metrics.PermitWaitDuration)
 | |
| 	if wantRes == "" {
 | |
| 		if m != nil {
 | |
| 			t.Errorf("PermitWaitDuration shouldn't be recorded but got %+v", m)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	if wantRes != "" {
 | |
| 		if len(m.Label) != 1 {
 | |
| 			t.Fatalf("Unexpected number of label pairs, got: %v, want: 1", len(m.Label))
 | |
| 		}
 | |
| 
 | |
| 		if *m.Label[0].Value != wantRes {
 | |
| 			t.Errorf("Unexpected result label, got: %q, want %q", *m.Label[0].Value, wantRes)
 | |
| 		}
 | |
| 
 | |
| 		if *m.Histogram.SampleCount != 1 {
 | |
| 			t.Errorf("Expect 1 sample, got: %v", *m.Histogram.SampleCount)
 | |
| 		}
 | |
| 
 | |
| 		if *m.Histogram.SampleSum <= 0 {
 | |
| 			t.Errorf("Expect latency to be greater than 0, got: %v", *m.Histogram.SampleSum)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func collectHistogramMetric(metric prometheus.Collector) *dto.Metric {
 | |
| 	ch := make(chan prometheus.Metric, 100)
 | |
| 	metric.Collect(ch)
 | |
| 	select {
 | |
| 	case got := <-ch:
 | |
| 		m := &dto.Metric{}
 | |
| 		got.Write(m)
 | |
| 		return m
 | |
| 	default:
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | 
