volcano.sh/volcano@v1.9.0/pkg/scheduler/actions/allocate/allocate_test.go (about)

     1  /*
     2  Copyright 2017 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 allocate
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"testing"
    24  
    25  	"github.com/agiledragon/gomonkey/v2"
    26  	v1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/resource"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/client-go/kubernetes/fake"
    30  	"k8s.io/client-go/tools/record"
    31  
    32  	"volcano.sh/volcano/pkg/scheduler/plugins/gang"
    33  	"volcano.sh/volcano/pkg/scheduler/plugins/priority"
    34  
    35  	storagev1 "k8s.io/api/storage/v1"
    36  
    37  	schedulingv1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1"
    38  	"volcano.sh/volcano/cmd/scheduler/app/options"
    39  	"volcano.sh/volcano/pkg/scheduler/api"
    40  	"volcano.sh/volcano/pkg/scheduler/cache"
    41  	"volcano.sh/volcano/pkg/scheduler/conf"
    42  	"volcano.sh/volcano/pkg/scheduler/framework"
    43  	"volcano.sh/volcano/pkg/scheduler/plugins/drf"
    44  	"volcano.sh/volcano/pkg/scheduler/plugins/proportion"
    45  	"volcano.sh/volcano/pkg/scheduler/util"
    46  )
    47  
    48  func TestAllocate(t *testing.T) {
    49  	var tmp *cache.SchedulerCache
    50  	patches := gomonkey.ApplyMethod(reflect.TypeOf(tmp), "AddBindTask", func(scCache *cache.SchedulerCache, task *api.TaskInfo) error {
    51  		scCache.Binder.Bind(nil, []*api.TaskInfo{task})
    52  		return nil
    53  	})
    54  	defer patches.Reset()
    55  
    56  	patchUpdateQueueStatus := gomonkey.ApplyMethod(reflect.TypeOf(tmp), "UpdateQueueStatus", func(scCache *cache.SchedulerCache, queue *api.QueueInfo) error {
    57  		return nil
    58  	})
    59  	defer patchUpdateQueueStatus.Reset()
    60  
    61  	framework.RegisterPluginBuilder("drf", drf.New)
    62  	framework.RegisterPluginBuilder("proportion", proportion.New)
    63  
    64  	options.ServerOpts = &options.ServerOption{
    65  		MinNodesToFind:             100,
    66  		MinPercentageOfNodesToFind: 5,
    67  		PercentageOfNodesToFind:    100,
    68  	}
    69  
    70  	defer framework.CleanupPluginBuilders()
    71  
    72  	tests := []struct {
    73  		name      string
    74  		podGroups []*schedulingv1.PodGroup
    75  		pods      []*v1.Pod
    76  		nodes     []*v1.Node
    77  		queues    []*schedulingv1.Queue
    78  		expected  map[string]string
    79  	}{
    80  		{
    81  			name: "one Job with two Pods on one node",
    82  			podGroups: []*schedulingv1.PodGroup{
    83  				util.BuildPodGroup("pg1", "c1", "c1", 0, nil, schedulingv1.PodGroupInqueue),
    84  			},
    85  			pods: []*v1.Pod{
    86  				util.BuildPod("c1", "p1", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg1", make(map[string]string), make(map[string]string)),
    87  				util.BuildPod("c1", "p2", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg1", make(map[string]string), make(map[string]string)),
    88  			},
    89  			nodes: []*v1.Node{
    90  				util.BuildNode("n1", api.BuildResourceList("2", "4Gi", []api.ScalarResource{{Name: "pods", Value: "10"}}...), make(map[string]string)),
    91  			},
    92  			queues: []*schedulingv1.Queue{
    93  				util.BuildQueue("c1", 1, nil),
    94  			},
    95  			expected: map[string]string{
    96  				"c1/p1": "n1",
    97  				"c1/p2": "n1",
    98  			},
    99  		},
   100  		{
   101  			name: "two Jobs on one node",
   102  			podGroups: []*schedulingv1.PodGroup{
   103  				util.BuildPodGroup("pg1", "c1", "c1", 0, nil, schedulingv1.PodGroupInqueue),
   104  				util.BuildPodGroup("pg2", "c2", "c2", 0, nil, schedulingv1.PodGroupInqueue),
   105  			},
   106  
   107  			// pod name should be like "*-*-{index}",
   108  			// due to change of TaskOrderFn
   109  			pods: []*v1.Pod{
   110  				// pending pod with owner1, under c1
   111  				util.BuildPod("c1", "pg1-p-1", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg1", make(map[string]string), make(map[string]string)),
   112  				// pending pod with owner1, under c1
   113  				util.BuildPod("c1", "pg1-p-2", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg1", make(map[string]string), make(map[string]string)),
   114  				// pending pod with owner2, under c2
   115  				util.BuildPod("c2", "pg2-p-1", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg2", make(map[string]string), make(map[string]string)),
   116  				// pending pod with owner2, under c2
   117  				util.BuildPod("c2", "pg2-p-2", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg2", make(map[string]string), make(map[string]string)),
   118  			},
   119  			nodes: []*v1.Node{
   120  				util.BuildNode("n1", api.BuildResourceList("2", "4G", []api.ScalarResource{{Name: "pods", Value: "10"}}...), make(map[string]string)),
   121  			},
   122  			queues: []*schedulingv1.Queue{
   123  				util.BuildQueue("c1", 1, nil),
   124  				util.BuildQueue("c2", 1, nil),
   125  			},
   126  			expected: map[string]string{
   127  				"c2/pg2-p-1": "n1",
   128  				"c1/pg1-p-1": "n1",
   129  			},
   130  		},
   131  		{
   132  			name: "high priority queue should not block others",
   133  			podGroups: []*schedulingv1.PodGroup{
   134  				util.BuildPodGroup("pg1", "c1", "c1", 0, nil, schedulingv1.PodGroupInqueue),
   135  				util.BuildPodGroup("pg2", "c1", "c2", 0, nil, schedulingv1.PodGroupInqueue),
   136  			},
   137  
   138  			pods: []*v1.Pod{
   139  				// pending pod with owner1, under ns:c1/q:c1
   140  				util.BuildPod("c1", "p1", "", v1.PodPending, api.BuildResourceList("3", "1G"), "pg1", make(map[string]string), make(map[string]string)),
   141  				// pending pod with owner2, under ns:c1/q:c2
   142  				util.BuildPod("c1", "p2", "", v1.PodPending, api.BuildResourceList("1", "1G"), "pg2", make(map[string]string), make(map[string]string)),
   143  			},
   144  			nodes: []*v1.Node{
   145  				util.BuildNode("n1", api.BuildResourceList("2", "4G", []api.ScalarResource{{Name: "pods", Value: "10"}}...), make(map[string]string)),
   146  			},
   147  			queues: []*schedulingv1.Queue{
   148  				util.BuildQueue("c1", 1, nil),
   149  				util.BuildQueue("c2", 1, nil),
   150  			},
   151  			expected: map[string]string{
   152  				"c1/p2": "n1",
   153  			},
   154  		},
   155  	}
   156  
   157  	allocate := New()
   158  
   159  	for _, test := range tests {
   160  		if test.name == "two Jobs on one node" {
   161  			// TODO(wangyang0616): First make sure that ut can run, and then fix the failed ut later
   162  			// See issue for details: https://github.com/volcano-sh/volcano/issues/2810
   163  			t.Skip("Test cases are not as expected, fixed later. see issue: #2810")
   164  		}
   165  		t.Run(test.name, func(t *testing.T) {
   166  			binder := &util.FakeBinder{
   167  				Binds:   map[string]string{},
   168  				Channel: make(chan string, 10),
   169  			}
   170  			schedulerCache := &cache.SchedulerCache{
   171  				Nodes:         make(map[string]*api.NodeInfo),
   172  				Jobs:          make(map[api.JobID]*api.JobInfo),
   173  				Queues:        make(map[api.QueueID]*api.QueueInfo),
   174  				Binder:        binder,
   175  				StatusUpdater: &util.FakeStatusUpdater{},
   176  				VolumeBinder:  &util.FakeVolumeBinder{},
   177  				Recorder:      record.NewFakeRecorder(100),
   178  			}
   179  
   180  			for _, node := range test.nodes {
   181  				schedulerCache.AddOrUpdateNode(node)
   182  			}
   183  			for _, pod := range test.pods {
   184  				schedulerCache.AddPod(pod)
   185  			}
   186  
   187  			for _, ss := range test.podGroups {
   188  				schedulerCache.AddPodGroupV1beta1(ss)
   189  			}
   190  
   191  			for _, q := range test.queues {
   192  				schedulerCache.AddQueueV1beta1(q)
   193  			}
   194  
   195  			trueValue := true
   196  			ssn := framework.OpenSession(schedulerCache, []conf.Tier{
   197  				{
   198  					Plugins: []conf.PluginOption{
   199  						{
   200  							Name:               "drf",
   201  							EnabledPreemptable: &trueValue,
   202  							EnabledJobOrder:    &trueValue,
   203  						},
   204  						{
   205  							Name:               "proportion",
   206  							EnabledQueueOrder:  &trueValue,
   207  							EnabledReclaimable: &trueValue,
   208  						},
   209  					},
   210  				},
   211  			}, nil)
   212  			defer framework.CloseSession(ssn)
   213  
   214  			allocate.Execute(ssn)
   215  
   216  			if !reflect.DeepEqual(test.expected, binder.Binds) {
   217  				t.Errorf("expected: %v, got %v ", test.expected, binder.Binds)
   218  			}
   219  		})
   220  	}
   221  }
   222  
   223  func TestAllocateWithDynamicPVC(t *testing.T) {
   224  	var tmp *cache.SchedulerCache
   225  	patches := gomonkey.ApplyMethod(reflect.TypeOf(tmp), "AddBindTask", func(scCache *cache.SchedulerCache, task *api.TaskInfo) error {
   226  		scCache.VolumeBinder.BindVolumes(task, task.PodVolumes)
   227  		scCache.Binder.Bind(nil, []*api.TaskInfo{task})
   228  		return nil
   229  	})
   230  	defer patches.Reset()
   231  
   232  	patchUpdateQueueStatus := gomonkey.ApplyMethod(reflect.TypeOf(tmp), "UpdateQueueStatus", func(scCache *cache.SchedulerCache, queue *api.QueueInfo) error {
   233  		return nil
   234  	})
   235  	defer patchUpdateQueueStatus.Reset()
   236  
   237  	framework.RegisterPluginBuilder("gang", gang.New)
   238  	framework.RegisterPluginBuilder("priority", priority.New)
   239  
   240  	options.ServerOpts = &options.ServerOption{
   241  		MinNodesToFind:             100,
   242  		MinPercentageOfNodesToFind: 5,
   243  		PercentageOfNodesToFind:    100,
   244  	}
   245  
   246  	defer framework.CleanupPluginBuilders()
   247  
   248  	queue := util.BuildQueue("c1", 1, nil)
   249  	pg := util.BuildPodGroup("pg1", "c1", "c1", 2, map[string]int32{"": 2}, schedulingv1.PodGroupInqueue)
   250  
   251  	pvc, _, sc := util.BuildDynamicPVC("c1", "pvc", v1.ResourceList{
   252  		v1.ResourceStorage: resource.MustParse("1Gi"),
   253  	})
   254  	pvc1 := pvc.DeepCopy()
   255  	pvc1.Name = fmt.Sprintf("pvc%d", 1)
   256  
   257  	allocate := New()
   258  
   259  	tests := []struct {
   260  		name            string
   261  		pods            []*v1.Pod
   262  		nodes           []*v1.Node
   263  		pvs             []*v1.PersistentVolume
   264  		pvcs            []*v1.PersistentVolumeClaim
   265  		sc              *storagev1.StorageClass
   266  		expectedBind    map[string]string
   267  		expectedActions map[string][]string
   268  	}{
   269  		{
   270  			name: "resource not match",
   271  			pods: []*v1.Pod{
   272  				util.BuildPodWithPVC("c1", "p1", "", v1.PodPending, api.BuildResourceList("1", "1G"), pvc, "pg1", make(map[string]string), make(map[string]string)),
   273  				util.BuildPodWithPVC("c1", "p2", "", v1.PodPending, api.BuildResourceList("1", "1G"), pvc1, "pg1", make(map[string]string), make(map[string]string)),
   274  			},
   275  			nodes: []*v1.Node{
   276  				util.BuildNode("n1", api.BuildResourceList("1", "4Gi", []api.ScalarResource{{Name: "pods", Value: "10"}}...), make(map[string]string)),
   277  			},
   278  			sc:           sc,
   279  			pvcs:         []*v1.PersistentVolumeClaim{pvc, pvc1},
   280  			expectedBind: map[string]string{},
   281  			expectedActions: map[string][]string{
   282  				"c1/p1": {"GetPodVolumes", "AllocateVolumes", "RevertVolumes"},
   283  			},
   284  		},
   285  		{
   286  			name: "node changed with enough resource",
   287  			pods: []*v1.Pod{
   288  				util.BuildPodWithPVC("c1", "p1", "", v1.PodPending, api.BuildResourceList("1", "1G"), pvc, "pg1", make(map[string]string), make(map[string]string)),
   289  				util.BuildPodWithPVC("c1", "p2", "", v1.PodPending, api.BuildResourceList("1", "1G"), pvc1, "pg1", make(map[string]string), make(map[string]string)),
   290  			},
   291  			nodes: []*v1.Node{
   292  				util.BuildNode("n2", api.BuildResourceList("2", "4Gi", []api.ScalarResource{{Name: "pods", Value: "10"}}...), make(map[string]string)),
   293  			},
   294  			sc:   sc,
   295  			pvcs: []*v1.PersistentVolumeClaim{pvc, pvc1},
   296  			expectedBind: map[string]string{
   297  				"c1/p1": "n2",
   298  				"c1/p2": "n2",
   299  			},
   300  			expectedActions: map[string][]string{
   301  				"c1/p1": {"GetPodVolumes", "AllocateVolumes", "DynamicProvisions"},
   302  				"c1/p2": {"GetPodVolumes", "AllocateVolumes", "DynamicProvisions"},
   303  			},
   304  		},
   305  	}
   306  
   307  	for _, test := range tests {
   308  		if test.name == "resource not match" {
   309  			// TODO(wangyang0616): First make sure that ut can run, and then fix the failed ut later
   310  			// See issue for details: https://github.com/volcano-sh/volcano/issues/2812
   311  			t.Skip("Test cases are not as expected, fixed later. see issue: #2812")
   312  		}
   313  		t.Run(test.name, func(t *testing.T) {
   314  			kubeClient := fake.NewSimpleClientset()
   315  			kubeClient.StorageV1().StorageClasses().Create(context.TODO(), test.sc, metav1.CreateOptions{})
   316  			for _, pv := range test.pvs {
   317  				kubeClient.CoreV1().PersistentVolumes().Create(context.TODO(), pv, metav1.CreateOptions{})
   318  			}
   319  			for _, pvc := range test.pvcs {
   320  				kubeClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(context.TODO(), pvc, metav1.CreateOptions{})
   321  			}
   322  
   323  			fakeVolumeBinder := util.NewFakeVolumeBinder(kubeClient)
   324  			binder := &util.FakeBinder{
   325  				Binds:   map[string]string{},
   326  				Channel: make(chan string, 10),
   327  			}
   328  			schedulerCache := &cache.SchedulerCache{
   329  				Nodes:         make(map[string]*api.NodeInfo),
   330  				Jobs:          make(map[api.JobID]*api.JobInfo),
   331  				Queues:        make(map[api.QueueID]*api.QueueInfo),
   332  				Binder:        binder,
   333  				StatusUpdater: &util.FakeStatusUpdater{},
   334  				VolumeBinder:  fakeVolumeBinder,
   335  				Recorder:      record.NewFakeRecorder(100),
   336  			}
   337  			schedulerCache.AddQueueV1beta1(queue)
   338  			schedulerCache.AddPodGroupV1beta1(pg)
   339  			for i, pod := range test.pods {
   340  				priority := int32(-i)
   341  				pod.Spec.Priority = &priority
   342  				schedulerCache.AddPod(pod)
   343  			}
   344  			for _, node := range test.nodes {
   345  				schedulerCache.AddOrUpdateNode(node)
   346  			}
   347  
   348  			trueValue := true
   349  			ssn := framework.OpenSession(schedulerCache, []conf.Tier{
   350  				{
   351  					Plugins: []conf.PluginOption{
   352  						{
   353  							Name:                "priority",
   354  							EnabledJobReady:     &trueValue,
   355  							EnabledPredicate:    &trueValue,
   356  							EnabledJobPipelined: &trueValue,
   357  							EnabledTaskOrder:    &trueValue,
   358  						},
   359  						{
   360  							Name:                "gang",
   361  							EnabledJobReady:     &trueValue,
   362  							EnabledPredicate:    &trueValue,
   363  							EnabledJobPipelined: &trueValue,
   364  							EnabledTaskOrder:    &trueValue,
   365  						},
   366  					},
   367  				},
   368  			}, nil)
   369  			defer framework.CloseSession(ssn)
   370  
   371  			allocate.Execute(ssn)
   372  			if !reflect.DeepEqual(test.expectedBind, binder.Binds) {
   373  				t.Errorf("expected: %v, got %v ", test.expectedBind, binder.Binds)
   374  			}
   375  			if !reflect.DeepEqual(test.expectedActions, fakeVolumeBinder.Actions) {
   376  				t.Errorf("expected: %v, got %v ", test.expectedActions, fakeVolumeBinder.Actions)
   377  			}
   378  			fakeVolumeBinder.Actions = make(map[string][]string)
   379  		})
   380  	}
   381  }