...

Source file src/k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources/most_allocated_test.go

Documentation: k8s.io/kubernetes/pkg/scheduler/framework/plugins/noderesources

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package noderesources
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	"k8s.io/klog/v2/ktesting"
    27  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    28  	"k8s.io/kubernetes/pkg/scheduler/framework"
    29  	plfeature "k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
    30  	"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
    31  	"k8s.io/kubernetes/pkg/scheduler/internal/cache"
    32  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    33  	tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
    34  )
    35  
    36  func TestMostAllocatedScoringStrategy(t *testing.T) {
    37  	tests := []struct {
    38  		name           string
    39  		requestedPod   *v1.Pod
    40  		nodes          []*v1.Node
    41  		existingPods   []*v1.Pod
    42  		expectedScores framework.NodeScoreList
    43  		resources      []config.ResourceSpec
    44  		wantErrs       field.ErrorList
    45  		wantStatusCode framework.Code
    46  	}{
    47  		{
    48  			// Node1 scores (used resources) on 0-MaxNodeScore scale
    49  			// CPU Score: (0 * MaxNodeScore)  / 4000 = 0
    50  			// Memory Score: (0 * MaxNodeScore) / 10000 = 0
    51  			// Node1 Score: (0 + 0) / 2 = 0
    52  			// Node2 scores (used resources) on 0-MaxNodeScore scale
    53  			// CPU Score: (0 * MaxNodeScore) / 4000 = 0
    54  			// Memory Score: (0 * MaxNodeScore) / 10000 = 0
    55  			// Node2 Score: (0 + 0) / 2 = 0
    56  			name:         "nothing scheduled, nothing requested",
    57  			requestedPod: st.MakePod().Obj(),
    58  			nodes: []*v1.Node{
    59  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
    60  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
    61  			},
    62  			existingPods:   nil,
    63  			expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MinNodeScore}, {Name: "node2", Score: framework.MinNodeScore}},
    64  			resources:      defaultResources,
    65  		},
    66  		{
    67  			// Node1 scores on 0-MaxNodeScore scale
    68  			// CPU Score: (3000 * MaxNodeScore) / 4000 = 75
    69  			// Memory Score: (5000 * MaxNodeScore) / 10000 = 50
    70  			// Node1 Score: (75 + 50) / 2 = 62
    71  			// Node2 scores on 0-MaxNodeScore scale
    72  			// CPU Score: (3000 * MaxNodeScore) / 6000 = 50
    73  			// Memory Score: (5000 * MaxNodeScore) / 10000 = 50
    74  			// Node2 Score: (50 + 50) / 2 = 50
    75  			name: "nothing scheduled, resources requested, differently sized nodes",
    76  			requestedPod: st.MakePod().
    77  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
    78  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
    79  				Obj(),
    80  			nodes: []*v1.Node{
    81  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
    82  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
    83  			},
    84  			existingPods:   nil,
    85  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 62}, {Name: "node2", Score: 50}},
    86  			resources:      defaultResources,
    87  		},
    88  		{
    89  			name: "Resources not set, pods scheduled with error",
    90  			requestedPod: st.MakePod().
    91  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
    92  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
    93  				Obj(),
    94  			nodes: []*v1.Node{
    95  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
    96  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
    97  			},
    98  			existingPods:   nil,
    99  			expectedScores: []framework.NodeScore{{Name: "node1", Score: framework.MinNodeScore}, {Name: "node2", Score: framework.MinNodeScore}},
   100  			resources:      nil,
   101  			wantStatusCode: framework.Error,
   102  		},
   103  		{
   104  			// Node1 scores on 0-MaxNodeScore scale
   105  			// CPU Score: (6000 * MaxNodeScore) / 10000 = 60
   106  			// Memory Score: (0 * MaxNodeScore) / 20000 = 0
   107  			// Node1 Score: (60 + 0) / 2 = 30
   108  			// Node2 scores on 0-MaxNodeScore scale
   109  			// CPU Score: (6000 * MaxNodeScore) / 10000 = 60
   110  			// Memory Score: (5000 * MaxNodeScore) / 20000 = 25
   111  			// Node2 Score: (60 + 25) / 2 = 42
   112  			name:         "no resources requested, pods scheduled with resources",
   113  			requestedPod: st.MakePod().Obj(),
   114  			nodes: []*v1.Node{
   115  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
   116  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
   117  			},
   118  			existingPods: []*v1.Pod{
   119  				st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
   120  				st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
   121  				st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
   122  				st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
   123  			},
   124  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 30}, {Name: "node2", Score: 42}},
   125  			resources:      defaultResources,
   126  		},
   127  		{
   128  			// Node1 scores on 0-MaxNodeScore scale
   129  			// CPU Score: (6000 * MaxNodeScore) / 10000 = 60
   130  			// Memory Score: (5000 * MaxNodeScore) / 20000 = 25
   131  			// Node1 Score: (60 + 25) / 2 = 42
   132  			// Node2 scores on 0-MaxNodeScore scale
   133  			// CPU Score: (6000 * MaxNodeScore) / 10000 = 60
   134  			// Memory Score: (10000 * MaxNodeScore) / 20000 = 50
   135  			// Node2 Score: (60 + 50) / 2 = 55
   136  			name: "resources requested, pods scheduled with resources",
   137  			requestedPod: st.MakePod().
   138  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
   139  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
   140  				Obj(),
   141  			nodes: []*v1.Node{
   142  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
   143  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "20000"}).Obj(),
   144  			},
   145  			existingPods: []*v1.Pod{
   146  				st.MakePod().Node("node1").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "0"}).Obj(),
   147  				st.MakePod().Node("node2").Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).Obj(),
   148  			},
   149  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 42}, {Name: "node2", Score: 55}},
   150  			resources:      defaultResources,
   151  		},
   152  		{
   153  			// Node1 scores on 0-MaxNodeScore scale
   154  			// CPU Score: 5000 * MaxNodeScore / 5000 return 100
   155  			// Memory Score: (9000 * MaxNodeScore) / 10000 = 90
   156  			// Node1 Score: (100 + 90) / 2 = 95
   157  			// Node2 scores on 0-MaxNodeScore scale
   158  			// CPU Score: (5000 * MaxNodeScore) / 10000 = 50
   159  			// Memory Score: 9000 * MaxNodeScore / 9000 return 100
   160  			// Node2 Score: (50 + 100) / 2 = 75
   161  			name: "resources requested equal node capacity",
   162  			requestedPod: st.MakePod().
   163  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "4000"}).
   164  				Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000"}).
   165  				Obj(),
   166  			nodes: []*v1.Node{
   167  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "5000", "memory": "10000"}).Obj(),
   168  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "10000", "memory": "9000"}).Obj(),
   169  			},
   170  			existingPods:   nil,
   171  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 95}, {Name: "node2", Score: 75}},
   172  			resources:      defaultResources,
   173  		},
   174  		{
   175  			// CPU Score: (3000 *100) / 4000 = 75
   176  			// Memory Score: (5000 *100) / 10000 = 50
   177  			// Node1 Score: (75 * 1 + 50 * 2) / (1 + 2) = 58
   178  			// CPU Score: (3000 *100) / 6000 = 50
   179  			// Memory Score: (5000 *100) / 10000 = 50
   180  			// Node2 Score: (50 * 1 + 50 * 2) / (1 + 2) = 50
   181  			name: "nothing scheduled, resources requested, differently sized nodes",
   182  			requestedPod: st.MakePod().
   183  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
   184  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
   185  				Obj(),
   186  			nodes: []*v1.Node{
   187  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
   188  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
   189  			},
   190  			existingPods:   nil,
   191  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 58}, {Name: "node2", Score: 50}},
   192  			resources: []config.ResourceSpec{
   193  				{Name: "memory", Weight: 2},
   194  				{Name: "cpu", Weight: 1},
   195  			},
   196  		},
   197  		{
   198  			// Node1 scores on 0-MaxNodeScore scale
   199  			// CPU Fraction: 300 / 250 = 100%
   200  			// Memory Fraction: 600 / 1000 = 60%
   201  			// Node1 Score: (100 + 60) / 2 = 80
   202  			// Node2 scores on 0-MaxNodeScore scale
   203  			// CPU Fraction: 100 / 250 = 40%
   204  			// Memory Fraction: 200 / 1000 = 20%
   205  			// Node2 Score: (20 + 40) / 2 = 30
   206  			name:         "no resources requested, pods scheduled, nonzero request for resource",
   207  			requestedPod: st.MakePod().Container("container").Obj(),
   208  			nodes: []*v1.Node{
   209  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "250m", "memory": "1000Mi"}).Obj(),
   210  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "250m", "memory": "1000Mi"}).Obj(),
   211  			},
   212  			existingPods: []*v1.Pod{
   213  				st.MakePod().Node("node1").Container("container").Obj(),
   214  				st.MakePod().Node("node1").Container("container").Obj(),
   215  			},
   216  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 80}, {Name: "node2", Score: 30}},
   217  			resources:      defaultResources,
   218  		},
   219  		{
   220  			// resource with negative weight is not allowed
   221  			name: "resource with negative weight",
   222  			requestedPod: st.MakePod().
   223  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
   224  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
   225  				Obj(),
   226  			nodes: []*v1.Node{
   227  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
   228  			},
   229  			resources: []config.ResourceSpec{
   230  				{Name: "memory", Weight: -1},
   231  				{Name: "cpu", Weight: 1},
   232  			},
   233  			wantErrs: field.ErrorList{
   234  				&field.Error{
   235  					Type:  field.ErrorTypeInvalid,
   236  					Field: "scoringStrategy.resources[0].weight",
   237  				},
   238  			},
   239  		},
   240  		{
   241  			// resource with zero weight is not allowed
   242  			name: "resource with zero weight",
   243  			requestedPod: st.MakePod().
   244  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
   245  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
   246  				Obj(),
   247  			nodes: []*v1.Node{
   248  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
   249  			},
   250  			resources: []config.ResourceSpec{
   251  				{Name: "memory", Weight: 1},
   252  				{Name: "cpu", Weight: 0},
   253  			},
   254  			wantErrs: field.ErrorList{
   255  				&field.Error{
   256  					Type:  field.ErrorTypeInvalid,
   257  					Field: "scoringStrategy.resources[1].weight",
   258  				},
   259  			},
   260  		},
   261  		{
   262  			// resource weight should be less than MaxNodeScore
   263  			name: "resource weight larger than MaxNodeScore",
   264  			requestedPod: st.MakePod().
   265  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
   266  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
   267  				Obj(),
   268  			nodes: []*v1.Node{
   269  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "4000", "memory": "10000"}).Obj(),
   270  			},
   271  			resources: []config.ResourceSpec{
   272  				{Name: "memory", Weight: 101},
   273  			},
   274  			wantErrs: field.ErrorList{
   275  				&field.Error{
   276  					Type:  field.ErrorTypeInvalid,
   277  					Field: "scoringStrategy.resources[0].weight",
   278  				},
   279  			},
   280  		},
   281  		{
   282  			// Bypass extended resource if the pod does not request.
   283  			// For both nodes: cpuScore and memScore are 50
   284  			// Given that extended resource score are intentionally bypassed,
   285  			// the final scores are:
   286  			// - node1: (50 + 50) / 2 = 50
   287  			// - node2: (50 + 50) / 2 = 50
   288  			name: "bypass extended resource if the pod does not request",
   289  			requestedPod: st.MakePod().
   290  				Req(map[v1.ResourceName]string{"cpu": "1000", "memory": "2000"}).
   291  				Req(map[v1.ResourceName]string{"cpu": "2000", "memory": "3000"}).
   292  				Obj(),
   293  			nodes: []*v1.Node{
   294  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
   295  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(),
   296  			},
   297  			resources:      extendedResourceSet,
   298  			existingPods:   nil,
   299  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 50}},
   300  		},
   301  		{
   302  			// Honor extended resource if the pod requests.
   303  			// For both nodes: cpuScore and memScore are 50.
   304  			// In terms of extended resource score:
   305  			// - node1 get: 2 / 4 * 100 = 50
   306  			// - node2 get: 2 / 10 * 100 = 20
   307  			// So the final scores are:
   308  			// - node1: (50 + 50 + 50) / 3 = 50
   309  			// - node2: (50 + 50 + 20) / 3 = 40
   310  			name: "honor extended resource if the pod request",
   311  			requestedPod: st.MakePod().Node("node1").
   312  				Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "5000", v1.ResourceName(extendedRes): "2"}).
   313  				Obj(),
   314  			nodes: []*v1.Node{
   315  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(),
   316  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "10"}).Obj(),
   317  			},
   318  			resources:      extendedResourceSet,
   319  			existingPods:   nil,
   320  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 50}, {Name: "node2", Score: 40}},
   321  		},
   322  		{
   323  			// If the node doesn't have a resource
   324  			// CPU Score: (3000 * 100) / 6000 = 50
   325  			// Memory Score: (4000 * 100) / 10000 = 40
   326  			// Node1 Score: (50 * 1 + 40 * 1) / (1 + 1) = 45
   327  			// Node2 Score: (50 * 1 + 40 * 1) / (1 + 1) = 45
   328  			name: "if the node doesn't have a resource",
   329  			requestedPod: st.MakePod().Node("node1").
   330  				Req(map[v1.ResourceName]string{"cpu": "3000", "memory": "4000"}).
   331  				Obj(),
   332  			nodes: []*v1.Node{
   333  				st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000"}).Obj(),
   334  				st.MakeNode().Name("node2").Capacity(map[v1.ResourceName]string{"cpu": "6000", "memory": "10000", v1.ResourceName(extendedRes): "4"}).Obj(),
   335  			},
   336  			expectedScores: []framework.NodeScore{{Name: "node1", Score: 45}, {Name: "node2", Score: 45}},
   337  			resources: []config.ResourceSpec{
   338  				{Name: extendedRes, Weight: 2},
   339  				{Name: string(v1.ResourceCPU), Weight: 1},
   340  				{Name: string(v1.ResourceMemory), Weight: 1},
   341  			},
   342  		},
   343  	}
   344  
   345  	for _, test := range tests {
   346  		t.Run(test.name, func(t *testing.T) {
   347  			_, ctx := ktesting.NewTestContext(t)
   348  			ctx, cancel := context.WithCancel(ctx)
   349  			defer cancel()
   350  
   351  			state := framework.NewCycleState()
   352  			snapshot := cache.NewSnapshot(test.existingPods, test.nodes)
   353  			fh, _ := runtime.NewFramework(ctx, nil, nil, runtime.WithSnapshotSharedLister(snapshot))
   354  
   355  			p, err := NewFit(ctx,
   356  				&config.NodeResourcesFitArgs{
   357  					ScoringStrategy: &config.ScoringStrategy{
   358  						Type:      config.MostAllocated,
   359  						Resources: test.resources,
   360  					},
   361  				}, fh, plfeature.Features{})
   362  
   363  			if diff := cmp.Diff(test.wantErrs.ToAggregate(), err, ignoreBadValueDetail); diff != "" {
   364  				t.Fatalf("got err (-want,+got):\n%s", diff)
   365  			}
   366  			if err != nil {
   367  				return
   368  			}
   369  
   370  			status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.requestedPod, tf.BuildNodeInfos(test.nodes))
   371  			if !status.IsSuccess() {
   372  				t.Errorf("PreScore is expected to return success, but didn't. Got status: %v", status)
   373  			}
   374  
   375  			var gotScores framework.NodeScoreList
   376  			for _, n := range test.nodes {
   377  				score, status := p.(framework.ScorePlugin).Score(ctx, state, test.requestedPod, n.Name)
   378  				if status.Code() != test.wantStatusCode {
   379  					t.Errorf("unexpected status code, want: %v, got: %v", test.wantStatusCode, status.Code())
   380  				}
   381  				gotScores = append(gotScores, framework.NodeScore{Name: n.Name, Score: score})
   382  			}
   383  
   384  			if diff := cmp.Diff(test.expectedScores, gotScores); diff != "" {
   385  				t.Errorf("Unexpected scores (-want,+got):\n%s", diff)
   386  			}
   387  		})
   388  	}
   389  }
   390  

View as plain text