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  }