github.com/kubevela/workflow@v0.6.0/pkg/utils/operation_test.go (about) 1 /* 2 Copyright 2022 The KubeVela 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 utils 18 19 import ( 20 "context" 21 "encoding/json" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/require" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 30 "github.com/kubevela/workflow/api/v1alpha1" 31 wfTypes "github.com/kubevela/workflow/pkg/types" 32 ) 33 34 func TestSuspendWorkflowRun(t *testing.T) { 35 ctx := context.Background() 36 37 testCases := map[string]struct { 38 run *v1alpha1.WorkflowRun 39 expected *v1alpha1.WorkflowRun 40 step string 41 expectedErr string 42 }{ 43 "terminated": { 44 run: &v1alpha1.WorkflowRun{ 45 ObjectMeta: metav1.ObjectMeta{ 46 Name: "terminated", 47 }, 48 Status: v1alpha1.WorkflowRunStatus{ 49 Terminated: true, 50 }, 51 }, 52 expectedErr: "can not suspend a terminated workflow", 53 }, 54 "already suspend": { 55 run: &v1alpha1.WorkflowRun{ 56 ObjectMeta: metav1.ObjectMeta{ 57 Name: "already-suspend", 58 }, 59 Status: v1alpha1.WorkflowRunStatus{ 60 Suspend: true, 61 }, 62 }, 63 expected: &v1alpha1.WorkflowRun{ 64 Status: v1alpha1.WorkflowRunStatus{ 65 Suspend: true, 66 }, 67 }, 68 }, 69 "not suspend": { 70 run: &v1alpha1.WorkflowRun{ 71 ObjectMeta: metav1.ObjectMeta{ 72 Name: "not-suspend", 73 }, 74 Status: v1alpha1.WorkflowRunStatus{ 75 Suspend: false, 76 }, 77 }, 78 expected: &v1alpha1.WorkflowRun{ 79 Status: v1alpha1.WorkflowRunStatus{ 80 Suspend: true, 81 }, 82 }, 83 }, 84 "step not found": { 85 run: &v1alpha1.WorkflowRun{ 86 ObjectMeta: metav1.ObjectMeta{ 87 Name: "step-not-found", 88 }, 89 Status: v1alpha1.WorkflowRunStatus{ 90 Suspend: false, 91 }, 92 }, 93 step: "not-found", 94 expectedErr: "can not find", 95 }, 96 "suspend all": { 97 run: &v1alpha1.WorkflowRun{ 98 ObjectMeta: metav1.ObjectMeta{ 99 Name: "suspend-all", 100 }, 101 Status: v1alpha1.WorkflowRunStatus{ 102 Steps: []v1alpha1.WorkflowStepStatus{ 103 { 104 StepStatus: v1alpha1.StepStatus{ 105 Name: "step1", 106 Phase: v1alpha1.WorkflowStepPhaseRunning, 107 }, 108 SubStepsStatus: []v1alpha1.StepStatus{ 109 { 110 Name: "sub1", 111 Phase: v1alpha1.WorkflowStepPhaseRunning, 112 }, 113 }, 114 }, 115 { 116 StepStatus: v1alpha1.StepStatus{ 117 Name: "step2", 118 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 119 }, 120 SubStepsStatus: []v1alpha1.StepStatus{ 121 { 122 Name: "sub2", 123 Phase: v1alpha1.WorkflowStepPhaseRunning, 124 }, 125 }, 126 }, 127 }, 128 }, 129 }, 130 expected: &v1alpha1.WorkflowRun{ 131 Status: v1alpha1.WorkflowRunStatus{ 132 Suspend: true, 133 Steps: []v1alpha1.WorkflowStepStatus{ 134 { 135 StepStatus: v1alpha1.StepStatus{ 136 Name: "step1", 137 Phase: v1alpha1.WorkflowStepPhaseSuspending, 138 }, 139 SubStepsStatus: []v1alpha1.StepStatus{ 140 { 141 Name: "sub1", 142 Phase: v1alpha1.WorkflowStepPhaseSuspending, 143 }, 144 }, 145 }, 146 { 147 StepStatus: v1alpha1.StepStatus{ 148 Name: "step2", 149 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 150 }, 151 SubStepsStatus: []v1alpha1.StepStatus{ 152 { 153 Name: "sub2", 154 Phase: v1alpha1.WorkflowStepPhaseSuspending, 155 }, 156 }, 157 }, 158 }, 159 }, 160 }, 161 }, 162 "suspend specific step": { 163 run: &v1alpha1.WorkflowRun{ 164 ObjectMeta: metav1.ObjectMeta{ 165 Name: "suspend-step", 166 }, 167 Status: v1alpha1.WorkflowRunStatus{ 168 Steps: []v1alpha1.WorkflowStepStatus{ 169 { 170 StepStatus: v1alpha1.StepStatus{ 171 Name: "step1", 172 Phase: v1alpha1.WorkflowStepPhaseRunning, 173 }, 174 SubStepsStatus: []v1alpha1.StepStatus{ 175 { 176 Name: "sub1", 177 Phase: v1alpha1.WorkflowStepPhaseRunning, 178 }, 179 }, 180 }, 181 }, 182 }, 183 }, 184 expected: &v1alpha1.WorkflowRun{ 185 Status: v1alpha1.WorkflowRunStatus{ 186 Suspend: true, 187 Steps: []v1alpha1.WorkflowStepStatus{ 188 { 189 StepStatus: v1alpha1.StepStatus{ 190 Name: "step1", 191 Phase: v1alpha1.WorkflowStepPhaseSuspending, 192 }, 193 SubStepsStatus: []v1alpha1.StepStatus{ 194 { 195 Name: "sub1", 196 Phase: v1alpha1.WorkflowStepPhaseSuspending, 197 }, 198 }, 199 }, 200 }, 201 }, 202 }, 203 step: "step1", 204 }, 205 "suspend specific sub step": { 206 run: &v1alpha1.WorkflowRun{ 207 ObjectMeta: metav1.ObjectMeta{ 208 Name: "suspend-sub-step", 209 }, 210 Status: v1alpha1.WorkflowRunStatus{ 211 Steps: []v1alpha1.WorkflowStepStatus{ 212 { 213 StepStatus: v1alpha1.StepStatus{ 214 Name: "step1", 215 Phase: v1alpha1.WorkflowStepPhaseRunning, 216 }, 217 SubStepsStatus: []v1alpha1.StepStatus{ 218 { 219 Name: "sub1", 220 Phase: v1alpha1.WorkflowStepPhaseRunning, 221 }, 222 }, 223 }, 224 }, 225 }, 226 }, 227 expected: &v1alpha1.WorkflowRun{ 228 Status: v1alpha1.WorkflowRunStatus{ 229 Suspend: true, 230 Steps: []v1alpha1.WorkflowStepStatus{ 231 { 232 StepStatus: v1alpha1.StepStatus{ 233 Name: "step1", 234 Phase: v1alpha1.WorkflowStepPhaseRunning, 235 }, 236 SubStepsStatus: []v1alpha1.StepStatus{ 237 { 238 Name: "sub1", 239 Phase: v1alpha1.WorkflowStepPhaseSuspending, 240 }, 241 }, 242 }, 243 }, 244 }, 245 }, 246 step: "sub1", 247 }, 248 } 249 for name, tc := range testCases { 250 t.Run(name, func(t *testing.T) { 251 r := require.New(t) 252 err := cli.Create(ctx, tc.run) 253 r.NoError(err) 254 defer func() { 255 err = cli.Delete(ctx, tc.run) 256 r.NoError(err) 257 }() 258 if tc.step != "" { 259 operator := NewWorkflowRunStepOperator(cli, nil, tc.run) 260 err = operator.Suspend(ctx, tc.step) 261 } else { 262 operator := NewWorkflowRunOperator(cli, nil, tc.run) 263 err = operator.Suspend(ctx) 264 } 265 if tc.expectedErr != "" { 266 r.Contains(err.Error(), tc.expectedErr) 267 return 268 } 269 r.NoError(err) 270 run := &v1alpha1.WorkflowRun{} 271 err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run) 272 r.NoError(err) 273 r.Equal(true, run.Status.Suspend) 274 r.Equal(tc.expected.Status, run.Status) 275 }) 276 } 277 } 278 279 func TestTerminateWorkflowRun(t *testing.T) { 280 ctx := context.Background() 281 282 testCases := map[string]struct { 283 run *v1alpha1.WorkflowRun 284 expected *v1alpha1.WorkflowRun 285 }{ 286 "suspend": { 287 run: &v1alpha1.WorkflowRun{ 288 ObjectMeta: metav1.ObjectMeta{ 289 Name: "suspend", 290 }, 291 Status: v1alpha1.WorkflowRunStatus{ 292 Suspend: true, 293 }, 294 }, 295 expected: &v1alpha1.WorkflowRun{ 296 Status: v1alpha1.WorkflowRunStatus{ 297 Terminated: true, 298 }, 299 }, 300 }, 301 "running step": { 302 run: &v1alpha1.WorkflowRun{ 303 ObjectMeta: metav1.ObjectMeta{ 304 Name: "running-step", 305 }, 306 Status: v1alpha1.WorkflowRunStatus{ 307 Steps: []v1alpha1.WorkflowStepStatus{ 308 { 309 StepStatus: v1alpha1.StepStatus{ 310 Phase: v1alpha1.WorkflowStepPhaseRunning, 311 }, 312 SubStepsStatus: []v1alpha1.StepStatus{ 313 { 314 Phase: v1alpha1.WorkflowStepPhaseRunning, 315 }, 316 { 317 Phase: v1alpha1.WorkflowStepPhaseFailed, 318 Reason: wfTypes.StatusReasonFailedAfterRetries, 319 }, 320 { 321 Phase: v1alpha1.WorkflowStepPhaseFailed, 322 Reason: wfTypes.StatusReasonTimeout, 323 }, 324 { 325 Phase: v1alpha1.WorkflowStepPhaseFailed, 326 Reason: wfTypes.StatusReasonExecute, 327 }, 328 }, 329 }, 330 { 331 StepStatus: v1alpha1.StepStatus{ 332 Phase: v1alpha1.WorkflowStepPhaseFailed, 333 Reason: wfTypes.StatusReasonFailedAfterRetries, 334 }, 335 }, 336 { 337 StepStatus: v1alpha1.StepStatus{ 338 Phase: v1alpha1.WorkflowStepPhaseFailed, 339 Reason: wfTypes.StatusReasonTimeout, 340 }, 341 }, 342 { 343 StepStatus: v1alpha1.StepStatus{ 344 Phase: v1alpha1.WorkflowStepPhaseFailed, 345 Reason: wfTypes.StatusReasonExecute, 346 }, 347 }, 348 }, 349 }, 350 }, 351 expected: &v1alpha1.WorkflowRun{ 352 Status: v1alpha1.WorkflowRunStatus{ 353 Steps: []v1alpha1.WorkflowStepStatus{ 354 { 355 StepStatus: v1alpha1.StepStatus{ 356 Phase: v1alpha1.WorkflowStepPhaseFailed, 357 Reason: wfTypes.StatusReasonTerminate, 358 }, 359 SubStepsStatus: []v1alpha1.StepStatus{ 360 { 361 Phase: v1alpha1.WorkflowStepPhaseFailed, 362 Reason: wfTypes.StatusReasonTerminate, 363 }, 364 { 365 Phase: v1alpha1.WorkflowStepPhaseFailed, 366 Reason: wfTypes.StatusReasonFailedAfterRetries, 367 }, 368 { 369 Phase: v1alpha1.WorkflowStepPhaseFailed, 370 Reason: wfTypes.StatusReasonTimeout, 371 }, 372 { 373 Phase: v1alpha1.WorkflowStepPhaseFailed, 374 Reason: wfTypes.StatusReasonTerminate, 375 }, 376 }, 377 }, 378 { 379 StepStatus: v1alpha1.StepStatus{ 380 Phase: v1alpha1.WorkflowStepPhaseFailed, 381 Reason: wfTypes.StatusReasonFailedAfterRetries, 382 }, 383 }, 384 { 385 StepStatus: v1alpha1.StepStatus{ 386 Phase: v1alpha1.WorkflowStepPhaseFailed, 387 Reason: wfTypes.StatusReasonTimeout, 388 }, 389 }, 390 { 391 StepStatus: v1alpha1.StepStatus{ 392 Phase: v1alpha1.WorkflowStepPhaseFailed, 393 Reason: wfTypes.StatusReasonTerminate, 394 }, 395 }, 396 }, 397 Terminated: true, 398 }, 399 }, 400 }, 401 } 402 for name, tc := range testCases { 403 t.Run(name, func(t *testing.T) { 404 r := require.New(t) 405 err := cli.Create(ctx, tc.run) 406 r.NoError(err) 407 defer func() { 408 err = cli.Delete(ctx, tc.run) 409 r.NoError(err) 410 }() 411 operator := NewWorkflowRunOperator(cli, nil, tc.run) 412 err = operator.Terminate(ctx) 413 r.NoError(err) 414 run := &v1alpha1.WorkflowRun{} 415 err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run) 416 r.NoError(err) 417 r.Equal(false, run.Status.Suspend) 418 r.Equal(true, run.Status.Terminated) 419 r.Equal(tc.expected.Status, run.Status) 420 }) 421 } 422 } 423 424 func TestResumeWorkflowRun(t *testing.T) { 425 ctx := context.Background() 426 427 testCases := map[string]struct { 428 run *v1alpha1.WorkflowRun 429 step string 430 expected *v1alpha1.WorkflowRun 431 expectedErr string 432 }{ 433 "not suspend": { 434 run: &v1alpha1.WorkflowRun{ 435 ObjectMeta: metav1.ObjectMeta{ 436 Name: "suspend", 437 }, 438 Status: v1alpha1.WorkflowRunStatus{ 439 Suspend: false, 440 }, 441 }, 442 expected: &v1alpha1.WorkflowRun{ 443 Status: v1alpha1.WorkflowRunStatus{}, 444 }, 445 }, 446 "suspend": { 447 run: &v1alpha1.WorkflowRun{ 448 ObjectMeta: metav1.ObjectMeta{ 449 Name: "suspend", 450 }, 451 Status: v1alpha1.WorkflowRunStatus{ 452 Suspend: true, 453 }, 454 }, 455 expected: &v1alpha1.WorkflowRun{ 456 Status: v1alpha1.WorkflowRunStatus{ 457 Suspend: false, 458 }, 459 }, 460 }, 461 "step not found": { 462 run: &v1alpha1.WorkflowRun{ 463 ObjectMeta: metav1.ObjectMeta{ 464 Name: "suspend", 465 }, 466 Status: v1alpha1.WorkflowRunStatus{ 467 Suspend: true, 468 }, 469 }, 470 step: "not-found", 471 expectedErr: "can not find step not-found", 472 }, 473 "suspend step": { 474 run: &v1alpha1.WorkflowRun{ 475 ObjectMeta: metav1.ObjectMeta{ 476 Name: "suspend-step", 477 }, 478 Status: v1alpha1.WorkflowRunStatus{ 479 Suspend: true, 480 Steps: []v1alpha1.WorkflowStepStatus{ 481 { 482 StepStatus: v1alpha1.StepStatus{ 483 Phase: v1alpha1.WorkflowStepPhaseSuspending, 484 }, 485 SubStepsStatus: []v1alpha1.StepStatus{ 486 { 487 Phase: v1alpha1.WorkflowStepPhaseSuspending, 488 }, 489 }, 490 }, 491 }, 492 }, 493 }, 494 expected: &v1alpha1.WorkflowRun{ 495 Status: v1alpha1.WorkflowRunStatus{ 496 Steps: []v1alpha1.WorkflowStepStatus{ 497 { 498 StepStatus: v1alpha1.StepStatus{ 499 Phase: v1alpha1.WorkflowStepPhaseRunning, 500 }, 501 SubStepsStatus: []v1alpha1.StepStatus{ 502 { 503 Phase: v1alpha1.WorkflowStepPhaseRunning, 504 }, 505 }, 506 }, 507 }, 508 }, 509 }, 510 }, 511 "resume the specific step": { 512 step: "step1", 513 run: &v1alpha1.WorkflowRun{ 514 ObjectMeta: metav1.ObjectMeta{ 515 Name: "resume-specific-step", 516 }, 517 Status: v1alpha1.WorkflowRunStatus{ 518 Suspend: true, 519 Steps: []v1alpha1.WorkflowStepStatus{ 520 { 521 StepStatus: v1alpha1.StepStatus{ 522 Name: "step1", 523 Phase: v1alpha1.WorkflowStepPhaseSuspending, 524 }, 525 }, 526 { 527 StepStatus: v1alpha1.StepStatus{ 528 Name: "step2", 529 Phase: v1alpha1.WorkflowStepPhaseSuspending, 530 }, 531 }, 532 }, 533 }, 534 }, 535 expected: &v1alpha1.WorkflowRun{ 536 Status: v1alpha1.WorkflowRunStatus{ 537 Steps: []v1alpha1.WorkflowStepStatus{ 538 { 539 StepStatus: v1alpha1.StepStatus{ 540 Name: "step1", 541 Phase: v1alpha1.WorkflowStepPhaseRunning, 542 }, 543 }, 544 { 545 StepStatus: v1alpha1.StepStatus{ 546 Name: "step2", 547 Phase: v1alpha1.WorkflowStepPhaseSuspending, 548 }, 549 }, 550 }, 551 }, 552 }, 553 }, 554 "resume the specific sub step": { 555 step: "sub-step1", 556 run: &v1alpha1.WorkflowRun{ 557 ObjectMeta: metav1.ObjectMeta{ 558 Name: "resume-specific-sub-step", 559 }, 560 Status: v1alpha1.WorkflowRunStatus{ 561 Suspend: true, 562 Steps: []v1alpha1.WorkflowStepStatus{ 563 { 564 StepStatus: v1alpha1.StepStatus{ 565 Name: "step1", 566 Phase: v1alpha1.WorkflowStepPhaseSuspending, 567 }, 568 SubStepsStatus: []v1alpha1.StepStatus{ 569 { 570 Name: "sub-step1", 571 Phase: v1alpha1.WorkflowStepPhaseSuspending, 572 }, 573 }, 574 }, 575 }, 576 }, 577 }, 578 expected: &v1alpha1.WorkflowRun{ 579 Status: v1alpha1.WorkflowRunStatus{ 580 Steps: []v1alpha1.WorkflowStepStatus{ 581 { 582 StepStatus: v1alpha1.StepStatus{ 583 Name: "step1", 584 Phase: v1alpha1.WorkflowStepPhaseSuspending, 585 }, 586 SubStepsStatus: []v1alpha1.StepStatus{ 587 { 588 Name: "sub-step1", 589 Phase: v1alpha1.WorkflowStepPhaseRunning, 590 }, 591 }, 592 }, 593 }, 594 }, 595 }, 596 }, 597 } 598 for name, tc := range testCases { 599 t.Run(name, func(t *testing.T) { 600 r := require.New(t) 601 err := cli.Create(ctx, tc.run) 602 r.NoError(err) 603 defer func() { 604 err = cli.Delete(ctx, tc.run) 605 r.NoError(err) 606 }() 607 if tc.step == "" { 608 operator := NewWorkflowRunOperator(cli, nil, tc.run) 609 err = operator.Resume(ctx) 610 if tc.expectedErr != "" { 611 r.Error(err) 612 r.Equal(tc.expectedErr, err.Error()) 613 return 614 } 615 } else { 616 operator := NewWorkflowRunStepOperator(cli, nil, tc.run) 617 err = operator.Resume(ctx, tc.step) 618 if tc.expectedErr != "" { 619 r.Error(err) 620 r.Equal(tc.expectedErr, err.Error()) 621 return 622 } 623 } 624 r.NoError(err) 625 run := &v1alpha1.WorkflowRun{} 626 err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run) 627 r.NoError(err) 628 r.Equal(false, run.Status.Suspend) 629 r.Equal(tc.expected.Status, run.Status, name) 630 }) 631 } 632 } 633 634 func TestRollbackWorkflowRun(t *testing.T) { 635 r := require.New(t) 636 operator := NewWorkflowRunOperator(cli, nil, nil) 637 err := operator.Rollback(context.Background()) 638 r.Equal("can not rollback a WorkflowRun", err.Error()) 639 } 640 641 func TestRestartRunStep(t *testing.T) { 642 ctx := context.Background() 643 644 testCases := map[string]struct { 645 run *v1alpha1.WorkflowRun 646 expected *v1alpha1.WorkflowRun 647 vars map[string]any 648 expectedVars string 649 stepName string 650 expectedErr string 651 }{ 652 "no step name": { 653 run: &v1alpha1.WorkflowRun{ 654 ObjectMeta: metav1.ObjectMeta{ 655 Name: "no-step-name", 656 }, 657 Status: v1alpha1.WorkflowRunStatus{ 658 ContextBackend: &corev1.ObjectReference{ 659 Name: "workflow-no-step-name-context", 660 Namespace: "default", 661 }, 662 Steps: []v1alpha1.WorkflowStepStatus{ 663 { 664 StepStatus: v1alpha1.StepStatus{ 665 Name: "exist", 666 }, 667 }, 668 }, 669 }, 670 }, 671 expected: &v1alpha1.WorkflowRun{ 672 ObjectMeta: metav1.ObjectMeta{ 673 Name: "no-step-name", 674 }, 675 Status: v1alpha1.WorkflowRunStatus{}, 676 }, 677 }, 678 "not found": { 679 run: &v1alpha1.WorkflowRun{ 680 ObjectMeta: metav1.ObjectMeta{ 681 Name: "not-found", 682 }, 683 Status: v1alpha1.WorkflowRunStatus{}, 684 }, 685 expectedErr: "not found", 686 stepName: "not-found", 687 }, 688 "not found2": { 689 run: &v1alpha1.WorkflowRun{ 690 ObjectMeta: metav1.ObjectMeta{ 691 Name: "not-found", 692 }, 693 Spec: v1alpha1.WorkflowRunSpec{ 694 WorkflowSpec: &v1alpha1.WorkflowSpec{ 695 Steps: []v1alpha1.WorkflowStep{}, 696 }, 697 }, 698 Status: v1alpha1.WorkflowRunStatus{}, 699 }, 700 expectedErr: "not found", 701 stepName: "not-found", 702 }, 703 "step not failed": { 704 run: &v1alpha1.WorkflowRun{ 705 ObjectMeta: metav1.ObjectMeta{ 706 Name: "step-not-failed", 707 }, 708 Spec: v1alpha1.WorkflowRunSpec{ 709 WorkflowSpec: &v1alpha1.WorkflowSpec{ 710 Steps: []v1alpha1.WorkflowStep{}, 711 }, 712 }, 713 Status: v1alpha1.WorkflowRunStatus{ 714 Steps: []v1alpha1.WorkflowStepStatus{ 715 { 716 StepStatus: v1alpha1.StepStatus{ 717 Name: "step-not-failed", 718 }, 719 }, 720 }, 721 }, 722 }, 723 stepName: "step-not-failed", 724 expectedErr: "can not restart from a non-failed step", 725 }, 726 "retry step in step-by-step": { 727 stepName: "step2", 728 vars: map[string]any{ 729 "step1-output1": "step1-output1", 730 "step2-output1": "step2-output1", 731 "step2-output2": "step2-output2", 732 "step3-output1": "step3-output1", 733 "step3-output2": "step3-output2", 734 }, 735 expectedVars: "{\n\t\"step1-output1\": \"step1-output1\"\n}", 736 run: &v1alpha1.WorkflowRun{ 737 ObjectMeta: metav1.ObjectMeta{ 738 Name: "retry-step", 739 }, 740 Spec: v1alpha1.WorkflowRunSpec{ 741 WorkflowSpec: &v1alpha1.WorkflowSpec{ 742 Steps: []v1alpha1.WorkflowStep{ 743 { 744 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 745 Name: "step1", 746 Outputs: v1alpha1.StepOutputs{ 747 { 748 Name: "step1-output1", 749 }, 750 }, 751 }, 752 }, 753 { 754 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 755 Name: "step2", 756 Outputs: v1alpha1.StepOutputs{ 757 { 758 Name: "step2-output1", 759 }, 760 { 761 Name: "step2-output2", 762 }, 763 }, 764 }, 765 }, 766 { 767 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 768 Name: "step3", 769 Outputs: v1alpha1.StepOutputs{ 770 { 771 Name: "step3-output1", 772 }, 773 { 774 Name: "step3-output2", 775 }, 776 }, 777 }, 778 }, 779 }, 780 }, 781 }, 782 Status: v1alpha1.WorkflowRunStatus{ 783 Terminated: true, 784 Finished: true, 785 Suspend: true, 786 EndTime: metav1.Time{Time: time.Now()}, 787 ContextBackend: &corev1.ObjectReference{ 788 Name: "workflow-retry-step-context", 789 Namespace: "default", 790 }, 791 Mode: v1alpha1.WorkflowExecuteMode{ 792 Steps: v1alpha1.WorkflowModeStep, 793 }, 794 Steps: []v1alpha1.WorkflowStepStatus{ 795 { 796 StepStatus: v1alpha1.StepStatus{ 797 Name: "step1", 798 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 799 }, 800 }, 801 { 802 StepStatus: v1alpha1.StepStatus{ 803 Name: "step2", 804 Phase: v1alpha1.WorkflowStepPhaseFailed, 805 }, 806 SubStepsStatus: []v1alpha1.StepStatus{ 807 { 808 Phase: v1alpha1.WorkflowStepPhaseRunning, 809 }, 810 }, 811 }, 812 { 813 StepStatus: v1alpha1.StepStatus{ 814 Name: "step3", 815 Phase: v1alpha1.WorkflowStepPhaseFailed, 816 }, 817 }, 818 }, 819 }, 820 }, 821 expected: &v1alpha1.WorkflowRun{ 822 Status: v1alpha1.WorkflowRunStatus{ 823 Mode: v1alpha1.WorkflowExecuteMode{ 824 Steps: v1alpha1.WorkflowModeStep, 825 }, 826 ContextBackend: &corev1.ObjectReference{ 827 Name: "workflow-retry-step-context", 828 Namespace: "default", 829 }, 830 Steps: []v1alpha1.WorkflowStepStatus{ 831 { 832 StepStatus: v1alpha1.StepStatus{ 833 Name: "step1", 834 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 835 }, 836 }, 837 }, 838 }, 839 }, 840 }, 841 "retry step in dag": { 842 stepName: "step2", 843 vars: map[string]any{ 844 "step2-output1": "step2-output1", 845 "step2-output2": "step2-output2", 846 }, 847 expectedVars: "{}", 848 run: &v1alpha1.WorkflowRun{ 849 ObjectMeta: metav1.ObjectMeta{ 850 Name: "retry-step", 851 }, 852 Spec: v1alpha1.WorkflowRunSpec{ 853 WorkflowSpec: &v1alpha1.WorkflowSpec{ 854 Steps: []v1alpha1.WorkflowStep{ 855 { 856 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 857 Name: "step1", 858 DependsOn: []string{"step3"}, 859 }, 860 }, 861 { 862 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 863 Name: "step2", 864 Outputs: v1alpha1.StepOutputs{ 865 { 866 Name: "step2-output1", 867 }, 868 { 869 Name: "step2-output2", 870 }, 871 }, 872 }, 873 }, 874 { 875 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 876 Name: "step3", 877 Inputs: v1alpha1.StepInputs{ 878 { 879 From: "step2-output1", 880 }, 881 }, 882 }, 883 }, 884 }, 885 }, 886 }, 887 Status: v1alpha1.WorkflowRunStatus{ 888 Terminated: true, 889 Finished: true, 890 Suspend: true, 891 EndTime: metav1.Time{Time: time.Now()}, 892 ContextBackend: &corev1.ObjectReference{ 893 Name: "workflow-retry-step-context", 894 Namespace: "default", 895 }, 896 Mode: v1alpha1.WorkflowExecuteMode{ 897 Steps: v1alpha1.WorkflowModeDAG, 898 }, 899 Steps: []v1alpha1.WorkflowStepStatus{ 900 { 901 StepStatus: v1alpha1.StepStatus{ 902 Name: "step1", 903 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 904 }, 905 }, 906 { 907 StepStatus: v1alpha1.StepStatus{ 908 Name: "step2", 909 Phase: v1alpha1.WorkflowStepPhaseFailed, 910 }, 911 SubStepsStatus: []v1alpha1.StepStatus{ 912 { 913 Phase: v1alpha1.WorkflowStepPhaseRunning, 914 }, 915 }, 916 }, 917 { 918 StepStatus: v1alpha1.StepStatus{ 919 Name: "step3", 920 Phase: v1alpha1.WorkflowStepPhaseFailed, 921 }, 922 }, 923 }, 924 }, 925 }, 926 expected: &v1alpha1.WorkflowRun{ 927 Status: v1alpha1.WorkflowRunStatus{ 928 Mode: v1alpha1.WorkflowExecuteMode{ 929 Steps: v1alpha1.WorkflowModeDAG, 930 }, 931 ContextBackend: &corev1.ObjectReference{ 932 Name: "workflow-retry-step-context", 933 Namespace: "default", 934 }, 935 }, 936 }, 937 }, 938 "retry sub step in step-by-step": { 939 stepName: "step2-sub2", 940 vars: map[string]any{ 941 "step2-output1": "step2-output1", 942 "step2-output2": "step2-output2", 943 "step2-sub2-output1": "step2-sub2-output1", 944 }, 945 expectedVars: "{\n\t\"step2-output1\": \"step2-output1\"\n\t\"step2-output2\": \"step2-output2\"\n}", 946 run: &v1alpha1.WorkflowRun{ 947 ObjectMeta: metav1.ObjectMeta{ 948 Name: "retry-sub-step", 949 }, 950 Spec: v1alpha1.WorkflowRunSpec{ 951 WorkflowSpec: &v1alpha1.WorkflowSpec{ 952 Steps: []v1alpha1.WorkflowStep{ 953 { 954 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 955 Name: "step1", 956 }, 957 }, 958 { 959 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 960 Name: "step2", 961 Outputs: v1alpha1.StepOutputs{ 962 { 963 Name: "step2-output1", 964 }, 965 { 966 Name: "step2-output2", 967 }, 968 }, 969 }, 970 SubSteps: []v1alpha1.WorkflowStepBase{ 971 { 972 Name: "step2-sub1", 973 }, 974 { 975 Name: "step2-sub2", 976 Outputs: v1alpha1.StepOutputs{ 977 { 978 Name: "step2-sub2-output1", 979 }, 980 }, 981 }, 982 { 983 Name: "step2-sub3", 984 }, 985 }, 986 }, 987 { 988 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 989 Name: "step3", 990 }, 991 }, 992 }, 993 }, 994 }, 995 Status: v1alpha1.WorkflowRunStatus{ 996 Terminated: true, 997 Finished: true, 998 Suspend: true, 999 EndTime: metav1.Time{Time: time.Now()}, 1000 Mode: v1alpha1.WorkflowExecuteMode{ 1001 Steps: v1alpha1.WorkflowModeStep, 1002 SubSteps: v1alpha1.WorkflowModeStep, 1003 }, 1004 ContextBackend: &corev1.ObjectReference{ 1005 Name: "workflow-retry-sub-step-context", 1006 Namespace: "default", 1007 }, 1008 Steps: []v1alpha1.WorkflowStepStatus{ 1009 { 1010 StepStatus: v1alpha1.StepStatus{ 1011 Name: "step1", 1012 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 1013 }, 1014 }, 1015 { 1016 StepStatus: v1alpha1.StepStatus{ 1017 Name: "step2", 1018 Phase: v1alpha1.WorkflowStepPhaseFailed, 1019 }, 1020 SubStepsStatus: []v1alpha1.StepStatus{ 1021 { 1022 Name: "step2-sub1", 1023 Phase: v1alpha1.WorkflowStepPhaseFailed, 1024 }, 1025 { 1026 Name: "step2-sub2", 1027 Phase: v1alpha1.WorkflowStepPhaseFailed, 1028 }, 1029 { 1030 Name: "step2-sub3", 1031 Phase: v1alpha1.WorkflowStepPhaseFailed, 1032 }, 1033 }, 1034 }, 1035 { 1036 StepStatus: v1alpha1.StepStatus{ 1037 Name: "step3", 1038 Phase: v1alpha1.WorkflowStepPhaseFailed, 1039 }, 1040 }, 1041 }, 1042 }, 1043 }, 1044 expected: &v1alpha1.WorkflowRun{ 1045 Status: v1alpha1.WorkflowRunStatus{ 1046 Mode: v1alpha1.WorkflowExecuteMode{ 1047 Steps: v1alpha1.WorkflowModeStep, 1048 SubSteps: v1alpha1.WorkflowModeStep, 1049 }, 1050 ContextBackend: &corev1.ObjectReference{ 1051 Name: "workflow-retry-sub-step-context", 1052 Namespace: "default", 1053 }, 1054 Steps: []v1alpha1.WorkflowStepStatus{ 1055 { 1056 StepStatus: v1alpha1.StepStatus{ 1057 Name: "step1", 1058 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 1059 }, 1060 }, 1061 { 1062 StepStatus: v1alpha1.StepStatus{ 1063 Name: "step2", 1064 Phase: v1alpha1.WorkflowStepPhaseRunning, 1065 }, 1066 SubStepsStatus: []v1alpha1.StepStatus{ 1067 { 1068 Name: "step2-sub1", 1069 Phase: v1alpha1.WorkflowStepPhaseFailed, 1070 }, 1071 }, 1072 }, 1073 }, 1074 }, 1075 }, 1076 }, 1077 "retry sub step in dag": { 1078 stepName: "step2-sub2", 1079 vars: map[string]any{ 1080 "step2-sub2-output1": "step2-sub2-output1", 1081 }, 1082 expectedVars: "{}", 1083 run: &v1alpha1.WorkflowRun{ 1084 ObjectMeta: metav1.ObjectMeta{ 1085 Name: "retry-sub-step", 1086 }, 1087 Spec: v1alpha1.WorkflowRunSpec{ 1088 WorkflowSpec: &v1alpha1.WorkflowSpec{ 1089 Steps: []v1alpha1.WorkflowStep{ 1090 { 1091 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 1092 Name: "step1", 1093 }, 1094 }, 1095 { 1096 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 1097 Name: "step2", 1098 }, 1099 SubSteps: []v1alpha1.WorkflowStepBase{ 1100 { 1101 Name: "step2-sub1", 1102 DependsOn: []string{"step2-sub2"}, 1103 }, 1104 { 1105 Name: "step2-sub2", 1106 Outputs: v1alpha1.StepOutputs{ 1107 { 1108 Name: "step2-sub2-output1", 1109 }, 1110 }, 1111 }, 1112 { 1113 Name: "step2-sub3", 1114 Inputs: v1alpha1.StepInputs{ 1115 { 1116 From: "step2-sub2-output1", 1117 }, 1118 }, 1119 }, 1120 }, 1121 }, 1122 { 1123 WorkflowStepBase: v1alpha1.WorkflowStepBase{ 1124 Name: "step3", 1125 DependsOn: []string{"step2"}, 1126 }, 1127 }, 1128 }, 1129 }, 1130 }, 1131 Status: v1alpha1.WorkflowRunStatus{ 1132 Terminated: true, 1133 Finished: true, 1134 Suspend: true, 1135 EndTime: metav1.Time{Time: time.Now()}, 1136 ContextBackend: &corev1.ObjectReference{ 1137 Name: "workflow-retry-sub-step-context", 1138 Namespace: "default", 1139 }, 1140 Mode: v1alpha1.WorkflowExecuteMode{ 1141 Steps: v1alpha1.WorkflowModeDAG, 1142 SubSteps: v1alpha1.WorkflowModeDAG, 1143 }, 1144 Steps: []v1alpha1.WorkflowStepStatus{ 1145 { 1146 StepStatus: v1alpha1.StepStatus{ 1147 Name: "step1", 1148 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 1149 }, 1150 }, 1151 { 1152 StepStatus: v1alpha1.StepStatus{ 1153 Name: "step2", 1154 Phase: v1alpha1.WorkflowStepPhaseFailed, 1155 }, 1156 SubStepsStatus: []v1alpha1.StepStatus{ 1157 { 1158 Name: "step2-sub1", 1159 Phase: v1alpha1.WorkflowStepPhaseFailed, 1160 }, 1161 { 1162 Name: "step2-sub2", 1163 Phase: v1alpha1.WorkflowStepPhaseFailed, 1164 }, 1165 { 1166 Name: "step2-sub3", 1167 Phase: v1alpha1.WorkflowStepPhaseFailed, 1168 }, 1169 }, 1170 }, 1171 { 1172 StepStatus: v1alpha1.StepStatus{ 1173 Name: "step3", 1174 Phase: v1alpha1.WorkflowStepPhaseFailed, 1175 }, 1176 }, 1177 }, 1178 }, 1179 }, 1180 expected: &v1alpha1.WorkflowRun{ 1181 Status: v1alpha1.WorkflowRunStatus{ 1182 Mode: v1alpha1.WorkflowExecuteMode{ 1183 Steps: v1alpha1.WorkflowModeDAG, 1184 SubSteps: v1alpha1.WorkflowModeDAG, 1185 }, 1186 ContextBackend: &corev1.ObjectReference{ 1187 Name: "workflow-retry-sub-step-context", 1188 Namespace: "default", 1189 }, 1190 Steps: []v1alpha1.WorkflowStepStatus{ 1191 { 1192 StepStatus: v1alpha1.StepStatus{ 1193 Name: "step1", 1194 Phase: v1alpha1.WorkflowStepPhaseSucceeded, 1195 }, 1196 }, 1197 { 1198 StepStatus: v1alpha1.StepStatus{ 1199 Name: "step2", 1200 Phase: v1alpha1.WorkflowStepPhaseRunning, 1201 }, 1202 }, 1203 }, 1204 }, 1205 }, 1206 }, 1207 } 1208 for name, tc := range testCases { 1209 t.Run(name, func(t *testing.T) { 1210 r := require.New(t) 1211 err := cli.Create(ctx, tc.run) 1212 r.NoError(err) 1213 defer func() { 1214 err = cli.Delete(ctx, tc.run) 1215 r.NoError(err) 1216 }() 1217 if tc.vars != nil { 1218 b, err := json.Marshal(tc.vars) 1219 r.NoError(err) 1220 cm := &corev1.ConfigMap{ 1221 ObjectMeta: metav1.ObjectMeta{ 1222 Name: tc.run.Status.ContextBackend.Name, 1223 Namespace: tc.run.Namespace, 1224 }, 1225 Data: map[string]string{ 1226 "vars": string(b), 1227 }, 1228 } 1229 err = cli.Create(ctx, cm) 1230 r.NoError(err) 1231 defer func() { 1232 err = cli.Delete(ctx, cm) 1233 r.NoError(err) 1234 }() 1235 } 1236 if tc.stepName == "" { 1237 operator := NewWorkflowRunOperator(cli, nil, tc.run) 1238 err = operator.Restart(ctx) 1239 if tc.expectedErr != "" { 1240 r.Contains(err.Error(), tc.expectedErr) 1241 return 1242 } 1243 r.NoError(err) 1244 } else { 1245 operator := NewWorkflowRunStepOperator(cli, nil, tc.run) 1246 err = operator.Restart(ctx, tc.stepName) 1247 if tc.expectedErr != "" { 1248 r.Contains(err.Error(), tc.expectedErr) 1249 return 1250 } 1251 r.NoError(err) 1252 } 1253 run := &v1alpha1.WorkflowRun{} 1254 err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run) 1255 r.NoError(err) 1256 r.Equal(false, run.Status.Suspend) 1257 r.Equal(false, run.Status.Terminated) 1258 r.Equal(false, run.Status.Finished) 1259 r.True(run.Status.EndTime.IsZero()) 1260 r.Equal(tc.expected.Status, run.Status) 1261 if tc.vars != nil { 1262 cm := &corev1.ConfigMap{} 1263 err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Status.ContextBackend.Name, Namespace: tc.run.Namespace}, cm) 1264 r.NoError(err) 1265 r.Equal(tc.expectedVars, cm.Data["vars"]) 1266 } 1267 }) 1268 } 1269 }