github.com/kubevela/workflow@v0.6.0/pkg/tasks/builtin/step_group.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 builtin 18 19 import ( 20 "context" 21 "fmt" 22 23 monitorContext "github.com/kubevela/pkg/monitor/context" 24 "github.com/kubevela/pkg/util/slices" 25 26 "github.com/kubevela/workflow/api/v1alpha1" 27 wfContext "github.com/kubevela/workflow/pkg/context" 28 "github.com/kubevela/workflow/pkg/cue/model/value" 29 "github.com/kubevela/workflow/pkg/cue/packages" 30 "github.com/kubevela/workflow/pkg/cue/process" 31 "github.com/kubevela/workflow/pkg/tasks/custom" 32 "github.com/kubevela/workflow/pkg/types" 33 ) 34 35 // StepGroup is the step group runner 36 func StepGroup(step v1alpha1.WorkflowStep, opt *types.TaskGeneratorOptions) (types.TaskRunner, error) { 37 return &stepGroupTaskRunner{ 38 id: opt.ID, 39 name: step.Name, 40 step: step, 41 subTaskRunners: opt.SubTaskRunners, 42 mode: opt.SubStepExecuteMode, 43 pd: opt.PackageDiscover, 44 pCtx: opt.ProcessContext, 45 }, nil 46 } 47 48 type stepGroupTaskRunner struct { 49 id string 50 name string 51 step v1alpha1.WorkflowStep 52 subTaskRunners []types.TaskRunner 53 pd *packages.PackageDiscover 54 pCtx process.Context 55 mode v1alpha1.WorkflowMode 56 } 57 58 // Name return suspend step name. 59 func (tr *stepGroupTaskRunner) Name() string { 60 return tr.name 61 } 62 63 // Pending check task should be executed or not. 64 func (tr *stepGroupTaskRunner) Pending(ctx monitorContext.Context, wfCtx wfContext.Context, stepStatus map[string]v1alpha1.StepStatus) (bool, v1alpha1.StepStatus) { 65 resetter := tr.FillContextData(ctx, tr.pCtx) 66 defer resetter(tr.pCtx) 67 basicVal, _, _ := custom.MakeBasicValue(wfCtx, "", tr.pCtx) 68 return custom.CheckPending(wfCtx, tr.step, tr.id, stepStatus, basicVal) 69 } 70 71 // Run make workflow step group. 72 func (tr *stepGroupTaskRunner) Run(ctx wfContext.Context, options *types.TaskRunOptions) (status v1alpha1.StepStatus, operations *types.Operation, rErr error) { 73 status = v1alpha1.StepStatus{ 74 ID: tr.id, 75 Name: tr.name, 76 Type: types.WorkflowStepTypeStepGroup, 77 Message: "", 78 } 79 80 pStatus := &status 81 if options.GetTracer == nil { 82 options.GetTracer = func(id string, step v1alpha1.WorkflowStep) monitorContext.Context { 83 return monitorContext.NewTraceContext(context.Background(), "") 84 } 85 } 86 tracer := options.GetTracer(tr.id, tr.step).AddTag("step_name", tr.name, "step_type", types.WorkflowStepTypeStepGroup) 87 resetter := tr.FillContextData(tracer, tr.pCtx) 88 defer resetter(tr.pCtx) 89 basicVal, basicTemplate, err := custom.MakeBasicValue(ctx, "", tr.pCtx) 90 if err != nil { 91 return status, nil, err 92 } 93 defer handleOutput(ctx, pStatus, operations, tr.step, options.PostStopHooks, basicVal) 94 95 for _, hook := range options.PreCheckHooks { 96 result, err := hook(tr.step, &types.PreCheckOptions{ 97 PackageDiscover: tr.pd, 98 BasicTemplate: basicTemplate, 99 BasicValue: basicVal, 100 }) 101 if err != nil { 102 status.Phase = v1alpha1.WorkflowStepPhaseSkipped 103 status.Reason = types.StatusReasonSkip 104 status.Message = fmt.Sprintf("pre check error: %s", err.Error()) 105 continue 106 } 107 if result.Skip { 108 status.Phase = v1alpha1.WorkflowStepPhaseSkipped 109 status.Reason = types.StatusReasonSkip 110 options.StepStatus[tr.step.Name] = status 111 break 112 } 113 if result.Timeout { 114 status.Phase = v1alpha1.WorkflowStepPhaseFailed 115 status.Reason = types.StatusReasonTimeout 116 options.StepStatus[tr.step.Name] = status 117 } 118 } 119 // step-group has no properties so there is no need to fill in the properties with the input values 120 // skip input handle here 121 e := options.Engine 122 if len(tr.subTaskRunners) > 0 { 123 e.SetParentRunner(tr.name) 124 dag := true 125 if tr.mode == v1alpha1.WorkflowModeStep { 126 dag = false 127 } 128 if err := e.Run(tracer, tr.subTaskRunners, dag); err != nil { 129 return v1alpha1.StepStatus{ 130 ID: tr.id, 131 Name: tr.name, 132 Type: types.WorkflowStepTypeStepGroup, 133 Phase: v1alpha1.WorkflowStepPhaseRunning, 134 }, e.GetOperation(), err 135 } 136 e.SetParentRunner("") 137 } 138 139 stepStatus := e.GetStepStatus(tr.name) 140 status, operations = getStepGroupStatus(status, stepStatus, e.GetOperation(), len(tr.subTaskRunners)) 141 142 return status, operations, nil 143 } 144 145 func (tr *stepGroupTaskRunner) FillContextData(ctx monitorContext.Context, processCtx process.Context) types.ContextDataResetter { 146 metas := []process.StepMetaKV{ 147 process.WithName(tr.name), 148 process.WithSessionID(tr.id), 149 process.WithSpanID(ctx.GetID()), 150 process.WithGroupName(tr.name), 151 } 152 manager := process.NewStepRunTimeMeta() 153 manager.Fill(processCtx, metas) 154 return func(processCtx process.Context) { 155 manager.Remove(processCtx, slices.Map(metas, 156 func(t process.StepMetaKV) string { 157 return t.Key 158 }), 159 ) 160 } 161 } 162 163 func getStepGroupStatus(status v1alpha1.StepStatus, stepStatus v1alpha1.WorkflowStepStatus, operation *types.Operation, subTaskRunners int) (v1alpha1.StepStatus, *types.Operation) { 164 subStepCounts := make(map[string]int) 165 for _, subStepsStatus := range stepStatus.SubStepsStatus { 166 subStepCounts[string(subStepsStatus.Phase)]++ 167 subStepCounts[subStepsStatus.Reason]++ 168 } 169 switch { 170 case status.Phase == v1alpha1.WorkflowStepPhaseSkipped: 171 return status, &types.Operation{Skip: true} 172 case status.Phase == v1alpha1.WorkflowStepPhaseFailed && status.Reason == types.StatusReasonTimeout: 173 return status, &types.Operation{Terminated: true} 174 case subStepCounts[string(v1alpha1.WorkflowStepPhaseSuspending)] > 0: 175 status.Phase = v1alpha1.WorkflowStepPhaseSuspending 176 case len(stepStatus.SubStepsStatus) < subTaskRunners: 177 status.Phase = v1alpha1.WorkflowStepPhaseRunning 178 case subStepCounts[string(v1alpha1.WorkflowStepPhaseRunning)] > 0: 179 status.Phase = v1alpha1.WorkflowStepPhaseRunning 180 case subStepCounts[string(v1alpha1.WorkflowStepPhasePending)] > 0: 181 status.Phase = v1alpha1.WorkflowStepPhasePending 182 case subStepCounts[string(v1alpha1.WorkflowStepPhaseFailed)] > 0: 183 status.Phase = v1alpha1.WorkflowStepPhaseFailed 184 switch { 185 case subStepCounts[types.StatusReasonFailedAfterRetries] > 0: 186 status.Reason = types.StatusReasonFailedAfterRetries 187 case subStepCounts[types.StatusReasonTimeout] > 0: 188 status.Reason = types.StatusReasonTimeout 189 case subStepCounts[types.StatusReasonAction] > 0: 190 status.Reason = types.StatusReasonAction 191 case subStepCounts[types.StatusReasonTerminate] > 0: 192 status.Reason = types.StatusReasonTerminate 193 } 194 case subStepCounts[string(v1alpha1.WorkflowStepPhaseSkipped)] > 0 && subStepCounts[string(v1alpha1.WorkflowStepPhaseSkipped)] == subTaskRunners: 195 status.Phase = v1alpha1.WorkflowStepPhaseSkipped 196 status.Reason = types.StatusReasonSkip 197 default: 198 status.Phase = v1alpha1.WorkflowStepPhaseSucceeded 199 } 200 return status, operation 201 } 202 203 func handleOutput(ctx wfContext.Context, stepStatus *v1alpha1.StepStatus, operations *types.Operation, step v1alpha1.WorkflowStep, postStopHooks []types.TaskPostStopHook, basicVal *value.Value) { 204 if len(step.Outputs) > 0 { 205 for _, hook := range postStopHooks { 206 if err := hook(ctx, basicVal, step, *stepStatus, nil); err != nil { 207 stepStatus.Phase = v1alpha1.WorkflowStepPhaseFailed 208 if stepStatus.Reason == "" { 209 stepStatus.Reason = types.StatusReasonOutput 210 } 211 operations.Terminated = true 212 stepStatus.Message = fmt.Sprintf("output error: %s", err.Error()) 213 return 214 } 215 } 216 } 217 }