volcano.sh/volcano@v1.9.0/pkg/controllers/jobflow/jobflow_controller_action_test.go (about) 1 /* 2 Copyright 2022 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 jobflow 18 19 import ( 20 "context" 21 "reflect" 22 "testing" 23 "time" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/client-go/informers" 27 kubeclient "k8s.io/client-go/kubernetes/fake" 28 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 29 30 "volcano.sh/apis/pkg/apis/batch/v1alpha1" 31 jobflowv1alpha1 "volcano.sh/apis/pkg/apis/flow/v1alpha1" 32 "volcano.sh/apis/pkg/apis/helpers" 33 volcanoclient "volcano.sh/apis/pkg/client/clientset/versioned/fake" 34 "volcano.sh/apis/pkg/client/clientset/versioned/scheme" 35 "volcano.sh/volcano/pkg/controllers/framework" 36 ) 37 38 func newFakeController() *jobflowcontroller { 39 volcanoClientSet := volcanoclient.NewSimpleClientset() 40 kubeClientSet := kubeclient.NewSimpleClientset() 41 42 sharedInformers := informers.NewSharedInformerFactory(kubeClientSet, 0) 43 44 controller := &jobflowcontroller{} 45 opt := &framework.ControllerOption{ 46 VolcanoClient: volcanoClientSet, 47 KubeClient: kubeClientSet, 48 SharedInformerFactory: sharedInformers, 49 WorkerNum: 3, 50 } 51 52 controller.Initialize(opt) 53 54 return controller 55 } 56 57 func TestSyncJobFlowFunc(t *testing.T) { 58 type args struct { 59 jobFlow *jobflowv1alpha1.JobFlow 60 jobTemplateList []*jobflowv1alpha1.JobTemplate 61 } 62 type wantRes struct { 63 jobFlowStatus *jobflowv1alpha1.JobFlowStatus 64 err error 65 } 66 tests := []struct { 67 name string 68 args args 69 want wantRes 70 }{ 71 { 72 name: "SyncJobFlow success case", 73 args: args{ 74 jobFlow: &jobflowv1alpha1.JobFlow{ 75 ObjectMeta: metav1.ObjectMeta{ 76 Name: "jobflow", 77 Namespace: "default", 78 }, 79 Spec: jobflowv1alpha1.JobFlowSpec{ 80 Flows: []jobflowv1alpha1.Flow{ 81 { 82 Name: "jobtemplate", 83 DependsOn: nil, 84 }, 85 }, 86 JobRetainPolicy: jobflowv1alpha1.Retain, 87 }, 88 }, 89 jobTemplateList: []*jobflowv1alpha1.JobTemplate{ 90 { 91 ObjectMeta: metav1.ObjectMeta{ 92 Name: "jobtemplate", 93 Namespace: "default", 94 }, 95 Spec: v1alpha1.JobSpec{}, 96 }, 97 }, 98 }, 99 want: wantRes{ 100 jobFlowStatus: &jobflowv1alpha1.JobFlowStatus{ 101 PendingJobs: make([]string, 0), 102 RunningJobs: []string{getJobName("jobflow", "jobtemplate")}, 103 FailedJobs: make([]string, 0), 104 CompletedJobs: make([]string, 0), 105 TerminatedJobs: make([]string, 0), 106 UnKnowJobs: make([]string, 0), 107 JobStatusList: []jobflowv1alpha1.JobStatus{ 108 { 109 Name: getJobName("jobflow", "jobtemplate"), 110 State: v1alpha1.Running, 111 StartTimestamp: metav1.Time{}, 112 EndTimestamp: metav1.Time{}, 113 RestartCount: 0, 114 RunningHistories: []jobflowv1alpha1.JobRunningHistory{ 115 { 116 StartTimestamp: metav1.Time{}, 117 EndTimestamp: metav1.Time{}, 118 State: v1alpha1.Running, 119 }, 120 }, 121 }, 122 }, 123 Conditions: map[string]jobflowv1alpha1.Condition{ 124 getJobName("jobflow", "jobtemplate"): { 125 Phase: v1alpha1.Running, 126 CreateTimestamp: metav1.Time{}, 127 RunningDuration: nil, 128 TaskStatusCount: nil, 129 }, 130 }, 131 State: jobflowv1alpha1.State{ 132 Phase: jobflowv1alpha1.Running, 133 }, 134 }, 135 err: nil, 136 }, 137 }, 138 } 139 for _, tt := range tests { 140 t.Run(tt.name, func(t *testing.T) { 141 fakeController := newFakeController() 142 for i := range tt.args.jobTemplateList { 143 if err := fakeController.jobTemplateInformer.Informer().GetIndexer().Add(tt.args.jobTemplateList[i]); err != nil { 144 t.Errorf("add jobTemplate to informerFake,error : %s", err.Error()) 145 } 146 job := &v1alpha1.Job{ 147 ObjectMeta: metav1.ObjectMeta{ 148 Name: getJobName(tt.args.jobFlow.Name, tt.args.jobTemplateList[i].Name), 149 Namespace: tt.args.jobFlow.Namespace, 150 Labels: map[string]string{CreatedByJobTemplate: GetTemplateString(tt.args.jobFlow.Namespace, tt.args.jobTemplateList[i].Name)}, 151 }, 152 Spec: tt.args.jobTemplateList[i].Spec, 153 Status: v1alpha1.JobStatus{ 154 State: v1alpha1.JobState{ 155 Phase: v1alpha1.Running, 156 }, 157 }, 158 } 159 if err := controllerutil.SetControllerReference(tt.args.jobFlow, job, scheme.Scheme); err != nil { 160 t.Errorf("SetControllerReference error : %s", err.Error()) 161 } 162 if err := fakeController.jobInformer.Informer().GetIndexer().Add(job); err != nil { 163 t.Errorf("add jobTemplate to informerFake,error : %s", err.Error()) 164 } 165 } 166 if _, err := fakeController.vcClient.FlowV1alpha1().JobFlows(tt.args.jobFlow.Namespace).Create(context.Background(), tt.args.jobFlow, metav1.CreateOptions{}); err != nil { 167 t.Errorf("create jobflow error : %s", err.Error()) 168 } 169 170 if got := fakeController.syncJobFlow(tt.args.jobFlow, func(status *jobflowv1alpha1.JobFlowStatus, allJobList int) { 171 if len(status.RunningJobs) > 0 || len(status.CompletedJobs) > 0 { 172 status.State.Phase = jobflowv1alpha1.Running 173 } else if len(status.FailedJobs) > 0 { 174 status.State.Phase = jobflowv1alpha1.Failed 175 } else { 176 status.State.Phase = jobflowv1alpha1.Pending 177 } 178 }); got != tt.want.err { 179 t.Error("Expected deleteAllJobsCreatedByJobFlow() return nil, but not nil") 180 } 181 for i := range tt.args.jobFlow.Status.JobStatusList { 182 for i2 := range tt.args.jobFlow.Status.JobStatusList[i].RunningHistories { 183 tt.args.jobFlow.Status.JobStatusList[i].RunningHistories[i2].StartTimestamp = metav1.Time{} 184 } 185 } 186 if !reflect.DeepEqual(&tt.args.jobFlow.Status, tt.want.jobFlowStatus) { 187 t.Error("not the expected result") 188 } 189 }) 190 } 191 } 192 193 func TestGetRunningHistoriesFunc(t *testing.T) { 194 type args struct { 195 jobStatusList []jobflowv1alpha1.JobStatus 196 job *v1alpha1.Job 197 } 198 startTime := time.Now() 199 endTime := startTime.Add(1 * time.Second) 200 tests := []struct { 201 name string 202 args args 203 want []jobflowv1alpha1.JobRunningHistory 204 }{ 205 { 206 name: "GetRunningHistories success case", 207 args: args{ 208 jobStatusList: []jobflowv1alpha1.JobStatus{ 209 { 210 Name: "vcJobA", 211 State: v1alpha1.Completed, 212 StartTimestamp: metav1.Time{Time: startTime}, 213 EndTimestamp: metav1.Time{Time: endTime}, 214 RestartCount: 0, 215 RunningHistories: []jobflowv1alpha1.JobRunningHistory{ 216 { 217 StartTimestamp: metav1.Time{Time: startTime}, 218 EndTimestamp: metav1.Time{Time: endTime}, 219 State: v1alpha1.Completed, 220 }, 221 }, 222 }, 223 }, 224 job: &v1alpha1.Job{ 225 TypeMeta: metav1.TypeMeta{}, 226 ObjectMeta: metav1.ObjectMeta{Name: "vcJobA"}, 227 Spec: v1alpha1.JobSpec{}, 228 Status: v1alpha1.JobStatus{ 229 State: v1alpha1.JobState{ 230 Phase: v1alpha1.Completed, 231 Reason: "", 232 Message: "", 233 LastTransitionTime: metav1.Time{}, 234 }, 235 }, 236 }, 237 }, 238 want: []jobflowv1alpha1.JobRunningHistory{ 239 { 240 StartTimestamp: metav1.Time{Time: startTime}, 241 EndTimestamp: metav1.Time{Time: endTime}, 242 State: v1alpha1.Completed, 243 }, 244 }, 245 }, 246 } 247 for _, tt := range tests { 248 t.Run(tt.name, func(t *testing.T) { 249 if got := getRunningHistories(tt.args.jobStatusList, tt.args.job); !reflect.DeepEqual(got, tt.want) { 250 t.Errorf("getRunningHistories() = %v, want %v", got, tt.want) 251 } 252 }) 253 } 254 } 255 256 func TestGetAllJobStatusFunc(t *testing.T) { 257 // TODO(wangyang0616): First make sure that ut can run, and then fix the failed ut later. 258 // See issue for details: https://github.com/volcano-sh/volcano/issues/2851 259 t.Skip("Test cases are not as expected, fixed later. see issue: #2851") 260 type args struct { 261 jobFlow *jobflowv1alpha1.JobFlow 262 allJobList *v1alpha1.JobList 263 } 264 createJobATime := time.Now() 265 jobFlowName := "jobFlowA" 266 createJobBTime := createJobATime.Add(time.Second) 267 tests := []struct { 268 name string 269 args args 270 want *jobflowv1alpha1.JobFlowStatus 271 wantErr bool 272 }{ 273 { 274 name: "GetAllJobStatus success case", 275 args: args{ 276 jobFlow: &jobflowv1alpha1.JobFlow{ 277 TypeMeta: metav1.TypeMeta{}, 278 ObjectMeta: metav1.ObjectMeta{ 279 Name: jobFlowName, 280 }, 281 Spec: jobflowv1alpha1.JobFlowSpec{ 282 Flows: []jobflowv1alpha1.Flow{ 283 { 284 Name: "A", 285 DependsOn: nil, 286 }, 287 { 288 Name: "B", 289 DependsOn: &jobflowv1alpha1.DependsOn{ 290 Targets: []string{"A"}, 291 }, 292 }, 293 }, 294 JobRetainPolicy: "", 295 }, 296 Status: jobflowv1alpha1.JobFlowStatus{}, 297 }, 298 allJobList: &v1alpha1.JobList{ 299 Items: []v1alpha1.Job{ 300 { 301 TypeMeta: metav1.TypeMeta{}, 302 ObjectMeta: metav1.ObjectMeta{ 303 Name: "jobFlowA-A", 304 CreationTimestamp: metav1.Time{Time: createJobATime}, 305 OwnerReferences: []metav1.OwnerReference{{ 306 APIVersion: "volcano", 307 Kind: JobFlow, 308 Name: jobFlowName, 309 }}, 310 }, 311 Spec: v1alpha1.JobSpec{}, 312 Status: v1alpha1.JobStatus{ 313 State: v1alpha1.JobState{Phase: v1alpha1.Completed}, 314 RetryCount: 1, 315 RunningDuration: &metav1.Duration{Duration: time.Second}, 316 }, 317 }, 318 { 319 TypeMeta: metav1.TypeMeta{}, 320 ObjectMeta: metav1.ObjectMeta{ 321 Name: "jobFlowA-B", 322 CreationTimestamp: metav1.Time{Time: createJobBTime}, 323 OwnerReferences: []metav1.OwnerReference{{ 324 APIVersion: "volcano", 325 Kind: JobFlow, 326 Name: jobFlowName, 327 }}, 328 }, 329 Spec: v1alpha1.JobSpec{}, 330 Status: v1alpha1.JobStatus{ 331 State: v1alpha1.JobState{Phase: v1alpha1.Running}, 332 }, 333 }, 334 }, 335 }, 336 }, 337 want: &jobflowv1alpha1.JobFlowStatus{ 338 PendingJobs: []string{}, 339 RunningJobs: []string{"jobFlowA-B"}, 340 FailedJobs: []string{}, 341 CompletedJobs: []string{"jobFlowA-A"}, 342 TerminatedJobs: []string{}, 343 UnKnowJobs: []string{}, 344 JobStatusList: []jobflowv1alpha1.JobStatus{ 345 { 346 Name: "jobFlowA-A", 347 State: v1alpha1.Completed, 348 StartTimestamp: metav1.Time{Time: createJobATime}, 349 EndTimestamp: metav1.Time{Time: createJobATime.Add(time.Second)}, 350 RestartCount: 1, 351 RunningHistories: []jobflowv1alpha1.JobRunningHistory{ 352 { 353 StartTimestamp: metav1.Time{}, 354 EndTimestamp: metav1.Time{}, 355 State: v1alpha1.Completed, 356 }, 357 }, 358 }, 359 { 360 Name: "jobFlowA-B", 361 State: v1alpha1.Running, 362 StartTimestamp: metav1.Time{Time: createJobBTime}, 363 EndTimestamp: metav1.Time{}, 364 RestartCount: 0, 365 RunningHistories: []jobflowv1alpha1.JobRunningHistory{ 366 { 367 StartTimestamp: metav1.Time{}, 368 EndTimestamp: metav1.Time{}, 369 State: v1alpha1.Running, 370 }, 371 }, 372 }, 373 }, 374 Conditions: map[string]jobflowv1alpha1.Condition{ 375 "jobFlowA-A": { 376 Phase: v1alpha1.Completed, 377 CreateTimestamp: metav1.Time{Time: createJobATime}, 378 RunningDuration: &metav1.Duration{Duration: time.Second}, 379 }, 380 "jobFlowA-B": { 381 Phase: v1alpha1.Running, 382 CreateTimestamp: metav1.Time{Time: createJobBTime}, 383 }, 384 }, 385 State: jobflowv1alpha1.State{Phase: ""}, 386 }, 387 wantErr: false, 388 }, 389 } 390 for _, tt := range tests { 391 t.Run(tt.name, func(t *testing.T) { 392 fakeController := newFakeController() 393 for i := range tt.args.allJobList.Items { 394 err := fakeController.jobInformer.Informer().GetIndexer().Add(&tt.args.allJobList.Items[i]) 395 if err != nil { 396 t.Error("Error While add vcjob") 397 } 398 } 399 400 got, err := fakeController.getAllJobStatus(tt.args.jobFlow) 401 if (err != nil) != tt.wantErr { 402 t.Errorf("getAllJobStatus() error = %v, wantErr %v", err, tt.wantErr) 403 return 404 } 405 if got != nil { 406 got.JobStatusList[0].RunningHistories[0].StartTimestamp = metav1.Time{} 407 got.JobStatusList[1].RunningHistories[0].StartTimestamp = metav1.Time{} 408 } 409 if !reflect.DeepEqual(got, tt.want) { 410 t.Errorf("getAllJobStatus() got = %v, want %v", got, tt.want) 411 } 412 }) 413 } 414 } 415 416 func TestLoadJobTemplateAndSetJobFunc(t *testing.T) { 417 type args struct { 418 jobFlow *jobflowv1alpha1.JobFlow 419 flowName string 420 jobName string 421 job *v1alpha1.Job 422 jobTemplate *jobflowv1alpha1.JobTemplate 423 } 424 type wantRes struct { 425 OwnerReference []metav1.OwnerReference 426 Annotations map[string]string 427 Labels map[string]string 428 Err error 429 } 430 flag := true 431 tests := []struct { 432 name string 433 args args 434 want wantRes 435 }{ 436 { 437 name: "LoadJobTemplateAndSetJob success case", 438 args: args{ 439 jobFlow: &jobflowv1alpha1.JobFlow{ 440 ObjectMeta: metav1.ObjectMeta{ 441 Name: "jobflow", 442 Namespace: "default", 443 }, 444 }, 445 flowName: "jobtemplate", 446 jobName: getJobName("jobflow", "jobtemplate"), 447 job: &v1alpha1.Job{}, 448 jobTemplate: &jobflowv1alpha1.JobTemplate{ 449 ObjectMeta: metav1.ObjectMeta{ 450 Name: "jobtemplate", 451 Namespace: "default", 452 }, 453 Spec: v1alpha1.JobSpec{}, 454 Status: jobflowv1alpha1.JobTemplateStatus{}, 455 }, 456 }, 457 want: wantRes{ 458 OwnerReference: []metav1.OwnerReference{ 459 { 460 APIVersion: helpers.JobFlowKind.Group + "/" + helpers.JobFlowKind.Version, 461 Kind: helpers.JobFlowKind.Kind, 462 Name: "jobflow", 463 UID: "", 464 Controller: &flag, 465 BlockOwnerDeletion: &flag, 466 }, 467 }, 468 Annotations: map[string]string{ 469 CreatedByJobTemplate: GetTemplateString("default", "jobtemplate"), 470 }, 471 Labels: map[string]string{ 472 CreatedByJobTemplate: GetTemplateString("default", "jobtemplate"), 473 }, 474 Err: nil, 475 }, 476 }, 477 } 478 for _, tt := range tests { 479 t.Run(tt.name, func(t *testing.T) { 480 fakeController := newFakeController() 481 err := fakeController.jobTemplateInformer.Informer().GetIndexer().Add(tt.args.jobTemplate) 482 if err != nil { 483 t.Error("Error While add vcjob") 484 } 485 486 if got := fakeController.loadJobTemplateAndSetJob(tt.args.jobFlow, tt.args.flowName, tt.args.jobName, tt.args.job); got != tt.want.Err { 487 t.Error("Expected loadJobTemplateAndSetJob() return nil, but not nil") 488 } 489 if !reflect.DeepEqual(tt.args.job.OwnerReferences, tt.want.OwnerReference) { 490 t.Error("not expected job OwnerReferences") 491 } 492 if !reflect.DeepEqual(tt.args.job.Annotations, tt.want.Annotations) { 493 t.Error("not expected job Annotations") 494 } 495 if !reflect.DeepEqual(tt.args.job.Labels, tt.want.Labels) { 496 t.Error("not expected job Annotations") 497 } 498 }) 499 } 500 } 501 502 func TestDeployJobFunc(t *testing.T) { 503 type args struct { 504 jobFlow *jobflowv1alpha1.JobFlow 505 jobTemplateList []*jobflowv1alpha1.JobTemplate 506 } 507 tests := []struct { 508 name string 509 args args 510 want error 511 }{ 512 { 513 name: "DeployJob success case", 514 args: args{ 515 jobFlow: &jobflowv1alpha1.JobFlow{ 516 ObjectMeta: metav1.ObjectMeta{ 517 Name: "jobflow", 518 Namespace: "default", 519 }, 520 Spec: jobflowv1alpha1.JobFlowSpec{ 521 Flows: []jobflowv1alpha1.Flow{ 522 { 523 Name: "jobtemplate", 524 DependsOn: nil, 525 }, 526 }, 527 JobRetainPolicy: jobflowv1alpha1.Retain, 528 }, 529 }, 530 jobTemplateList: []*jobflowv1alpha1.JobTemplate{ 531 { 532 ObjectMeta: metav1.ObjectMeta{ 533 Name: "jobtemplate", 534 Namespace: "default", 535 }, 536 Spec: v1alpha1.JobSpec{}, 537 Status: jobflowv1alpha1.JobTemplateStatus{}, 538 }, 539 }, 540 }, 541 want: nil, 542 }, 543 } 544 for _, tt := range tests { 545 t.Run(tt.name, func(t *testing.T) { 546 fakeController := newFakeController() 547 for i := range tt.args.jobTemplateList { 548 err := fakeController.jobTemplateInformer.Informer().GetIndexer().Add(tt.args.jobTemplateList[i]) 549 if err != nil { 550 t.Error("Error While add jobTemplate") 551 } 552 } 553 554 if got := fakeController.deployJob(tt.args.jobFlow); got != tt.want { 555 t.Error("Expected deployJob() return nil, but not nil") 556 } 557 }) 558 } 559 } 560 561 func TestDeleteAllJobsCreateByJobFlowFunc(t *testing.T) { 562 type args struct { 563 jobFlow *jobflowv1alpha1.JobFlow 564 jobList []*v1alpha1.Job 565 } 566 tests := []struct { 567 name string 568 args args 569 want error 570 }{ 571 { 572 name: "LoadJobTemplateAndSetJob success case", 573 args: args{ 574 jobFlow: &jobflowv1alpha1.JobFlow{ 575 ObjectMeta: metav1.ObjectMeta{ 576 Name: "jobflow", 577 Namespace: "default", 578 }, 579 Spec: jobflowv1alpha1.JobFlowSpec{ 580 Flows: []jobflowv1alpha1.Flow{ 581 { 582 Name: "jobtemplate", 583 DependsOn: nil, 584 }, 585 }, 586 JobRetainPolicy: jobflowv1alpha1.Retain, 587 }, 588 }, 589 jobList: []*v1alpha1.Job{ 590 { 591 ObjectMeta: metav1.ObjectMeta{ 592 Name: "jobtemplate", 593 Namespace: "default", 594 }, 595 Spec: v1alpha1.JobSpec{}, 596 }, 597 }, 598 }, 599 want: nil, 600 }, 601 } 602 for _, tt := range tests { 603 t.Run(tt.name, func(t *testing.T) { 604 fakeController := newFakeController() 605 for i := range tt.args.jobList { 606 _, err := fakeController.vcClient.BatchV1alpha1().Jobs(tt.args.jobList[i].Namespace).Create(context.Background(), tt.args.jobList[i], metav1.CreateOptions{}) 607 if err != nil { 608 t.Error("Error While create vcjob") 609 } 610 } 611 612 if got := fakeController.deleteAllJobsCreatedByJobFlow(tt.args.jobFlow); got != tt.want { 613 t.Error("Expected deleteAllJobsCreatedByJobFlow() return nil, but not nil") 614 } 615 }) 616 } 617 }