volcano.sh/volcano@v1.9.0/pkg/controllers/job/job_controller_actions_test.go (about) 1 /* 2 Copyright 2019 The Volcano 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 job 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "github.com/agiledragon/gomonkey/v2" 24 v1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "reflect" 27 "testing" 28 29 "volcano.sh/apis/pkg/apis/batch/v1alpha1" 30 schedulingapi "volcano.sh/apis/pkg/apis/scheduling/v1beta1" 31 "volcano.sh/volcano/pkg/controllers/apis" 32 "volcano.sh/volcano/pkg/controllers/job/state" 33 ) 34 35 func TestKillJobFunc(t *testing.T) { 36 namespace := "test" 37 38 testcases := []struct { 39 Name string 40 Job *v1alpha1.Job 41 PodGroup *schedulingapi.PodGroup 42 PodRetainPhase state.PhaseMap 43 UpdateStatus state.UpdateStatusFn 44 JobInfo *apis.JobInfo 45 Services []v1.Service 46 ConfigMaps []v1.ConfigMap 47 Secrets []v1.Secret 48 Pods map[string]*v1.Pod 49 Plugins []string 50 ExpectVal error 51 }{ 52 { 53 Name: "KillJob success Case", 54 Job: &v1alpha1.Job{ 55 ObjectMeta: metav1.ObjectMeta{ 56 Name: "job1", 57 Namespace: namespace, 58 UID: "e7f18111-1cec-11ea-b688-fa163ec79500", 59 ResourceVersion: "100", 60 }, 61 }, 62 PodGroup: &schedulingapi.PodGroup{ 63 ObjectMeta: metav1.ObjectMeta{ 64 Name: "job1-e7f18111-1cec-11ea-b688-fa163ec79500", 65 Namespace: namespace, 66 }, 67 }, 68 PodRetainPhase: state.PodRetainPhaseNone, 69 UpdateStatus: nil, 70 JobInfo: &apis.JobInfo{ 71 Namespace: namespace, 72 Name: "jobinfo1", 73 Pods: map[string]map[string]*v1.Pod{ 74 "task1": { 75 "pod1": buildPod(namespace, "pod1", v1.PodRunning, nil), 76 "pod2": buildPod(namespace, "pod2", v1.PodRunning, nil), 77 }, 78 }, 79 }, 80 Services: []v1.Service{ 81 { 82 ObjectMeta: metav1.ObjectMeta{ 83 Name: "job1", 84 Namespace: namespace, 85 }, 86 }, 87 }, 88 Secrets: []v1.Secret{ 89 { 90 ObjectMeta: metav1.ObjectMeta{ 91 Name: "job1-e7f18111-1cec-11ea-b688-fa163ec79500-ssh", 92 Namespace: namespace, 93 }, 94 }, 95 }, 96 Pods: map[string]*v1.Pod{ 97 "pod1": buildPod(namespace, "pod1", v1.PodRunning, nil), 98 "pod2": buildPod(namespace, "pod2", v1.PodRunning, nil), 99 }, 100 Plugins: []string{"svc", "ssh", "env"}, 101 ExpectVal: nil, 102 }, 103 } 104 105 for i, testcase := range testcases { 106 107 t.Run(testcase.Name, func(t *testing.T) { 108 fakeController := newFakeController() 109 jobPlugins := make(map[string][]string) 110 111 for _, service := range testcase.Services { 112 _, err := fakeController.kubeClient.CoreV1().Services(namespace).Create(context.TODO(), &service, metav1.CreateOptions{}) 113 if err != nil { 114 t.Error("Error While Creating Service") 115 } 116 } 117 118 for _, secret := range testcase.Secrets { 119 _, err := fakeController.kubeClient.CoreV1().Secrets(namespace).Create(context.TODO(), &secret, metav1.CreateOptions{}) 120 if err != nil { 121 t.Error("Error While Creating Secret.") 122 } 123 } 124 125 for _, pod := range testcase.Pods { 126 _, err := fakeController.kubeClient.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 127 if err != nil { 128 t.Error("Error While Creating ConfigMaps") 129 } 130 } 131 132 _, err := fakeController.vcClient.BatchV1alpha1().Jobs(namespace).Create(context.TODO(), testcase.Job, metav1.CreateOptions{}) 133 if err != nil { 134 t.Error("Error While Creating Jobs") 135 } 136 err = fakeController.cache.Add(testcase.Job) 137 if err != nil { 138 t.Error("Error While Adding Job in cache") 139 } 140 141 for _, plugin := range testcase.Plugins { 142 jobPlugins[plugin] = make([]string, 0) 143 } 144 145 testcase.JobInfo.Job = testcase.Job 146 testcase.JobInfo.Job.Spec.Plugins = jobPlugins 147 148 testcase.JobInfo.Job.Status.ControlledResources = map[string]string{} 149 for _, name := range testcase.Plugins { 150 testcase.JobInfo.Job.Status.ControlledResources["plugin-"+name] = name 151 } 152 153 err = fakeController.killJob(testcase.JobInfo, testcase.PodRetainPhase, testcase.UpdateStatus) 154 if err != nil { 155 t.Errorf("Case %d (%s): expected: No Error, but got error %v.", i, testcase.Name, err) 156 } 157 158 for _, plugin := range testcase.Plugins { 159 160 if plugin == "svc" { 161 _, err = fakeController.kubeClient.CoreV1().Services(namespace).Get(context.TODO(), testcase.Job.Name, metav1.GetOptions{}) 162 if err == nil { 163 t.Errorf("Case %d (%s): expected: Service to be deleted, but not deleted.", i, testcase.Name) 164 } 165 } 166 167 if plugin == "ssh" { 168 _, err := fakeController.kubeClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), 169 fmt.Sprintf("%s-%s-%s", testcase.Job.Name, testcase.Job.UID, "ssh"), metav1.GetOptions{}) 170 if err == nil { 171 t.Errorf("Case %d (%s): expected: Secret to be deleted, but not deleted.", i, testcase.Name) 172 } 173 } 174 } 175 }) 176 } 177 } 178 179 func TestSyncJobFunc(t *testing.T) { 180 namespace := "test" 181 182 testcases := []struct { 183 Name string 184 Job *v1alpha1.Job 185 PodGroup *schedulingapi.PodGroup 186 PodRetainPhase state.PhaseMap 187 UpdateStatus state.UpdateStatusFn 188 JobInfo *apis.JobInfo 189 Pods map[string]*v1.Pod 190 Plugins []string 191 TotalNumPods int 192 ExpectVal error 193 }{ 194 { 195 Name: "SyncJob success Case", 196 Job: &v1alpha1.Job{ 197 ObjectMeta: metav1.ObjectMeta{ 198 Name: "job1", 199 Namespace: namespace, 200 ResourceVersion: "100", 201 UID: "e7f18111-1cec-11ea-b688-fa163ec79500", 202 }, 203 Spec: v1alpha1.JobSpec{ 204 Tasks: []v1alpha1.TaskSpec{ 205 { 206 Name: "task1", 207 Replicas: 6, 208 Template: v1.PodTemplateSpec{ 209 ObjectMeta: metav1.ObjectMeta{ 210 Name: "pods", 211 Namespace: namespace, 212 }, 213 Spec: v1.PodSpec{ 214 Containers: []v1.Container{ 215 { 216 Name: "Containers", 217 }, 218 }, 219 }, 220 }, 221 }, 222 }, 223 }, 224 Status: v1alpha1.JobStatus{ 225 State: v1alpha1.JobState{ 226 Phase: v1alpha1.Pending, 227 }, 228 }, 229 }, 230 PodGroup: &schedulingapi.PodGroup{ 231 ObjectMeta: metav1.ObjectMeta{ 232 Name: "job1-e7f18111-1cec-11ea-b688-fa163ec79500", 233 Namespace: namespace, 234 }, 235 Spec: schedulingapi.PodGroupSpec{ 236 MinResources: &v1.ResourceList{}, 237 MinTaskMember: map[string]int32{}, 238 }, 239 Status: schedulingapi.PodGroupStatus{ 240 Phase: schedulingapi.PodGroupInqueue, 241 }, 242 }, 243 PodRetainPhase: state.PodRetainPhaseNone, 244 UpdateStatus: nil, 245 JobInfo: &apis.JobInfo{ 246 Namespace: namespace, 247 Name: "jobinfo1", 248 Pods: map[string]map[string]*v1.Pod{ 249 "task1": { 250 "job1-task1-0": buildPod(namespace, "job1-task1-0", v1.PodRunning, nil), 251 "job1-task1-1": buildPod(namespace, "job1-task1-1", v1.PodRunning, nil), 252 }, 253 }, 254 }, 255 Pods: map[string]*v1.Pod{ 256 "job1-task1-0": buildPod(namespace, "job1-task1-0", v1.PodRunning, nil), 257 "job1-task1-1": buildPod(namespace, "job1-task1-1", v1.PodRunning, nil), 258 }, 259 TotalNumPods: 6, 260 Plugins: []string{"svc", "ssh", "env"}, 261 ExpectVal: nil, 262 }, 263 { 264 Name: "SyncJob with dependsOn job can't find the dependent task", 265 /* 266 Work dependsOn Master task, preempt actions causes controller deadlock 267 controller master,work scheduler 268 | | <---preempt----- | 269 | | <---kill work--- | 270 | ----watch work kill----> | | 271 | | <---kill master-- | 272 | ----create work pods---> | | 273 | --wait master running--> | | 274 | | | 275 | ---watch master kill---> | | 276 | --push master to queue-> | | 277 | -wait process to create-> | | 278 */ 279 Job: &v1alpha1.Job{ 280 ObjectMeta: metav1.ObjectMeta{ 281 Name: "job1", 282 Namespace: namespace, 283 ResourceVersion: "100", 284 UID: "e7f18111-1cec-11ea-b688-fa163ec79500", 285 }, 286 Spec: v1alpha1.JobSpec{ 287 Tasks: []v1alpha1.TaskSpec{ 288 { 289 Name: "master", 290 Replicas: 1, 291 Template: v1.PodTemplateSpec{ 292 ObjectMeta: metav1.ObjectMeta{ 293 Name: "pods", 294 Namespace: namespace, 295 }, 296 Spec: v1.PodSpec{ 297 Containers: []v1.Container{ 298 { 299 Name: "Containers", 300 }, 301 }, 302 }, 303 }, 304 }, 305 { 306 Name: "work", 307 Replicas: 3, 308 Template: v1.PodTemplateSpec{ 309 ObjectMeta: metav1.ObjectMeta{ 310 Name: "pods", 311 Namespace: namespace, 312 }, 313 Spec: v1.PodSpec{ 314 Containers: []v1.Container{ 315 { 316 Name: "Containers", 317 }, 318 }, 319 }, 320 }, 321 DependsOn: &v1alpha1.DependsOn{ 322 Name: []string{"master"}, 323 }, 324 }, 325 }, 326 }, 327 Status: v1alpha1.JobStatus{ 328 State: v1alpha1.JobState{ 329 Phase: v1alpha1.Pending, 330 }, 331 }, 332 }, 333 PodGroup: &schedulingapi.PodGroup{ 334 ObjectMeta: metav1.ObjectMeta{ 335 Name: "job1-e7f18111-1cec-11ea-b688-fa163ec79500", 336 Namespace: namespace, 337 }, 338 Spec: schedulingapi.PodGroupSpec{ 339 MinResources: &v1.ResourceList{}, 340 MinTaskMember: map[string]int32{}, 341 }, 342 Status: schedulingapi.PodGroupStatus{ 343 Phase: schedulingapi.PodGroupInqueue, 344 }, 345 }, 346 PodRetainPhase: state.PodRetainPhaseNone, 347 UpdateStatus: nil, 348 JobInfo: &apis.JobInfo{ 349 Namespace: namespace, 350 Name: "jobinfo1", 351 Pods: map[string]map[string]*v1.Pod{ 352 "work": { 353 "job1-work-0": buildPod(namespace, "job1-work-0", v1.PodRunning, nil), 354 "job1-work-1": buildPod(namespace, "job1-work-1", v1.PodRunning, nil), 355 }, 356 }, 357 }, 358 Pods: map[string]*v1.Pod{ 359 "job1-work-0": buildPod(namespace, "job1-work-0", v1.PodRunning, nil), 360 "job1-work-1": buildPod(namespace, "job1-work-1", v1.PodRunning, nil), 361 }, 362 TotalNumPods: 4, 363 Plugins: []string{"svc", "ssh", "env"}, 364 ExpectVal: nil, 365 }, 366 } 367 for i, testcase := range testcases { 368 369 t.Run(testcase.Name, func(t *testing.T) { 370 fakeController := newFakeController() 371 372 patches := gomonkey.ApplyMethod(reflect.TypeOf(fakeController), "GetQueueInfo", func(_ *jobcontroller, _ string) (*schedulingapi.Queue, error) { 373 return &schedulingapi.Queue{}, nil 374 }) 375 376 defer patches.Reset() 377 378 jobPlugins := make(map[string][]string) 379 380 for _, plugin := range testcase.Plugins { 381 jobPlugins[plugin] = make([]string, 0) 382 } 383 testcase.JobInfo.Job = testcase.Job 384 testcase.JobInfo.Job.Spec.Plugins = jobPlugins 385 386 fakeController.pgInformer.Informer().GetIndexer().Add(testcase.PodGroup) 387 fakeController.vcClient.SchedulingV1beta1().PodGroups(testcase.PodGroup.Namespace).Create(context.TODO(), testcase.PodGroup, metav1.CreateOptions{}) 388 389 for _, pod := range testcase.Pods { 390 _, err := fakeController.kubeClient.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 391 if err != nil { 392 t.Error("Error While Creating pods") 393 } 394 } 395 396 _, err := fakeController.vcClient.BatchV1alpha1().Jobs(namespace).Create(context.TODO(), testcase.Job, metav1.CreateOptions{}) 397 if err != nil { 398 t.Errorf("Expected no Error while creating job, but got error: %s", err) 399 } 400 401 err = fakeController.cache.Add(testcase.Job) 402 if err != nil { 403 t.Error("Error While Adding Job in cache") 404 } 405 406 err = fakeController.syncJob(testcase.JobInfo, nil) 407 if err != testcase.ExpectVal { 408 t.Errorf("Expected no error while syncing job, but got error: %s", err) 409 } 410 411 podList, err := fakeController.kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) 412 if err != nil { 413 t.Errorf("Expected no error while listing pods, but got error %s in case %d", err, i) 414 } 415 if testcase.TotalNumPods != len(podList.Items) { 416 t.Errorf("Expected Total number of pods to be same as podlist count: Expected: %d, Got: %d in case: %d", testcase.TotalNumPods, len(podList.Items), i) 417 } 418 }) 419 } 420 } 421 422 func TestCreateJobIOIfNotExistFunc(t *testing.T) { 423 namespace := "test" 424 425 testcases := []struct { 426 Name string 427 Job *v1alpha1.Job 428 ExpextVal error 429 }{ 430 { 431 Name: "Create Job IO case", 432 Job: &v1alpha1.Job{ 433 ObjectMeta: metav1.ObjectMeta{ 434 Name: "job1", 435 Namespace: namespace, 436 ResourceVersion: "100", 437 }, 438 Spec: v1alpha1.JobSpec{ 439 Volumes: []v1alpha1.VolumeSpec{ 440 { 441 VolumeClaimName: "pvc1", 442 }, 443 }, 444 }, 445 }, 446 ExpextVal: errors.New("pvc pvc1 is not found, the job will be in the Pending state until the PVC is created"), 447 }, 448 } 449 450 for i, testcase := range testcases { 451 452 t.Run(testcase.Name, func(t *testing.T) { 453 fakeController := newFakeController() 454 455 job, err := fakeController.createJobIOIfNotExist(testcase.Job) 456 if testcase.ExpextVal == nil { 457 if err != nil { 458 t.Errorf("Expected Return value to be : %v, but got: %v in testcase %d", testcase.ExpextVal, err, i) 459 } 460 } else { 461 if err == nil || err.Error() != testcase.ExpextVal.Error() { 462 t.Errorf("Expected Return value to be : %v, but got: %v in testcase %d", testcase.ExpextVal.Error(), err.Error(), i) 463 } 464 } 465 466 if len(job.Spec.Volumes) == 0 { 467 t.Errorf("Expected number of volumes to be greater than 0 but got: %d in case: %d", len(job.Spec.Volumes), i) 468 } 469 }) 470 } 471 } 472 473 func TestCreatePVCFunc(t *testing.T) { 474 namespace := "test" 475 476 testcases := []struct { 477 Name string 478 Job *v1alpha1.Job 479 VolumeClaim *v1.PersistentVolumeClaimSpec 480 ExpextVal error 481 }{ 482 { 483 Name: "CreatePVC success Case", 484 Job: &v1alpha1.Job{ 485 ObjectMeta: metav1.ObjectMeta{ 486 Name: "job1", 487 Namespace: namespace, 488 ResourceVersion: "100", 489 }, 490 }, 491 VolumeClaim: &v1.PersistentVolumeClaimSpec{ 492 VolumeName: "vol1", 493 }, 494 ExpextVal: nil, 495 }, 496 } 497 498 for _, testcase := range testcases { 499 t.Run(testcase.Name, func(t *testing.T) { 500 fakeController := newFakeController() 501 502 err := fakeController.createPVC(testcase.Job, "pvc1", testcase.VolumeClaim) 503 if err != testcase.ExpextVal { 504 t.Errorf("Expected return value to be equal to expected: %s, but got: %s", testcase.ExpextVal, err) 505 } 506 _, err = fakeController.kubeClient.CoreV1().PersistentVolumeClaims(namespace).Get(context.TODO(), "pvc1", metav1.GetOptions{}) 507 if err != nil { 508 t.Error("Expected PVC to get created, but not created") 509 } 510 }) 511 } 512 } 513 514 func TestCreatePodGroupIfNotExistFunc(t *testing.T) { 515 namespace := "test" 516 517 testcases := []struct { 518 Name string 519 Job *v1alpha1.Job 520 ExpextVal error 521 }{ 522 { 523 Name: "CreatePodGroup success Case", 524 Job: &v1alpha1.Job{ 525 ObjectMeta: metav1.ObjectMeta{ 526 Namespace: namespace, 527 Name: "job1", 528 ResourceVersion: "100", 529 UID: "e7f18111-1cec-11ea-b688-fa163ec79500", 530 }, 531 }, 532 ExpextVal: nil, 533 }, 534 } 535 536 for _, testcase := range testcases { 537 t.Run(testcase.Name, func(t *testing.T) { 538 fakeController := newFakeController() 539 540 err := fakeController.createOrUpdatePodGroup(testcase.Job) 541 if err != testcase.ExpextVal { 542 t.Errorf("Expected return value to be equal to expected: %s, but got: %s", testcase.ExpextVal, err) 543 } 544 545 pgName := testcase.Job.Name + "-" + string(testcase.Job.UID) 546 _, err = fakeController.vcClient.SchedulingV1beta1().PodGroups(namespace).Get(context.TODO(), pgName, metav1.GetOptions{}) 547 if err != nil { 548 t.Error("Expected PodGroup to get created, but not created") 549 } 550 }) 551 552 } 553 } 554 555 func TestUpdatePodGroupIfJobUpdateFunc(t *testing.T) { 556 namespace := "test" 557 558 testcases := []struct { 559 Name string 560 PodGroup *schedulingapi.PodGroup 561 Job *v1alpha1.Job 562 ExpectVal error 563 }{ 564 { 565 Name: "UpdatePodGroup success Case", 566 PodGroup: &schedulingapi.PodGroup{ 567 ObjectMeta: metav1.ObjectMeta{ 568 Namespace: namespace, 569 Name: "job1-e7f18111-1cec-11ea-b688-fa163ec79500", 570 }, 571 Spec: schedulingapi.PodGroupSpec{ 572 MinResources: &v1.ResourceList{}, 573 }, 574 }, 575 Job: &v1alpha1.Job{ 576 ObjectMeta: metav1.ObjectMeta{ 577 Namespace: namespace, 578 Name: "job1", 579 ResourceVersion: "100", 580 UID: "e7f18111-1cec-11ea-b688-fa163ec79500", 581 }, 582 Spec: v1alpha1.JobSpec{ 583 PriorityClassName: "new", 584 }, 585 }, 586 ExpectVal: nil, 587 }, 588 } 589 590 for _, testcase := range testcases { 591 t.Run(testcase.Name, func(t *testing.T) { 592 fakeController := newFakeController() 593 fakeController.pgInformer.Informer().GetIndexer().Add(testcase.PodGroup) 594 fakeController.vcClient.SchedulingV1beta1().PodGroups(testcase.PodGroup.Namespace).Create(context.TODO(), testcase.PodGroup, metav1.CreateOptions{}) 595 596 err := fakeController.createOrUpdatePodGroup(testcase.Job) 597 if err != testcase.ExpectVal { 598 t.Errorf("Expected return value to be equal to expected: %s, but got: %s", testcase.ExpectVal, err) 599 } 600 601 pgName := testcase.Job.Name + "-" + string(testcase.Job.UID) 602 pg, err := fakeController.vcClient.SchedulingV1beta1().PodGroups(namespace).Get(context.TODO(), pgName, metav1.GetOptions{}) 603 if err != nil { 604 t.Error("Expected PodGroup to be created, but not created") 605 } 606 if pg.Spec.PriorityClassName != testcase.Job.Spec.PriorityClassName { 607 t.Errorf("Expected PodGroup.Spec.PriorityClassName to be updated to: %s, but got: %s", testcase.Job.Spec.PriorityClassName, pg.Spec.PriorityClassName) 608 } 609 610 }) 611 612 } 613 614 } 615 616 func TestDeleteJobPod(t *testing.T) { 617 namespace := "test" 618 619 testcases := []struct { 620 Name string 621 Job *v1alpha1.Job 622 Pods map[string]*v1.Pod 623 DeletePod *v1.Pod 624 ExpextVal error 625 }{ 626 { 627 Name: "DeleteJobPod success case", 628 Job: &v1alpha1.Job{ 629 ObjectMeta: metav1.ObjectMeta{ 630 Name: "job1", 631 Namespace: namespace, 632 ResourceVersion: "100", 633 }, 634 }, 635 Pods: map[string]*v1.Pod{ 636 "job1-task1-0": buildPod(namespace, "job1-task1-0", v1.PodRunning, nil), 637 "job1-task1-1": buildPod(namespace, "job1-task1-1", v1.PodRunning, nil), 638 }, 639 DeletePod: buildPod(namespace, "job1-task1-0", v1.PodRunning, nil), 640 ExpextVal: nil, 641 }, 642 } 643 644 for _, testcase := range testcases { 645 t.Run(testcase.Name, func(t *testing.T) { 646 fakeController := newFakeController() 647 648 for _, pod := range testcase.Pods { 649 _, err := fakeController.kubeClient.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 650 if err != nil { 651 t.Error("Expected error not to occur") 652 } 653 } 654 655 err := fakeController.deleteJobPod(testcase.Job.Name, testcase.DeletePod) 656 if err != testcase.ExpextVal { 657 t.Errorf("Expected return value to be equal to expected: %s, but got: %s", testcase.ExpextVal, err) 658 } 659 660 _, err = fakeController.kubeClient.CoreV1().Pods(namespace).Get(context.TODO(), "job1-task1-0", metav1.GetOptions{}) 661 if err == nil { 662 t.Error("Expected Pod to be deleted but not deleted") 663 } 664 }) 665 } 666 }