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 }