diff --git a/apis/core.oam.dev/common/types.go b/apis/core.oam.dev/common/types.go index c0437a35ecb6815162829302a142a2f964d3103a..473cf4c7d014d36e2bd58d70ce5d86e08d8fb783 100644 --- a/apis/core.oam.dev/common/types.go +++ b/apis/core.oam.dev/common/types.go @@ -268,8 +268,8 @@ type RawComponent struct { Raw runtime.RawExtension `json:"raw"` } -// WorkflowStepStatus record the status of a workflow step -type WorkflowStepStatus struct { +// StepStatus record the base status of workflow step, which could be workflow step or subStep +type StepStatus struct { ID string `json:"id"` Name string `json:"name,omitempty"` Type string `json:"type,omitempty"` @@ -277,24 +277,22 @@ type WorkflowStepStatus struct { // A human readable message indicating details about why the workflowStep is in this state. Message string `json:"message,omitempty"` // A brief CamelCase message indicating details about why the workflowStep is in this state. - Reason string `json:"reason,omitempty"` - SubSteps *SubStepsStatus `json:"subSteps,omitempty"` + Reason string `json:"reason,omitempty"` // FirstExecuteTime is the first time this step execution. FirstExecuteTime metav1.Time `json:"firstExecuteTime,omitempty"` // LastExecuteTime is the last time this step execution. LastExecuteTime metav1.Time `json:"lastExecuteTime,omitempty"` } -// WorkflowSubStepStatus record the status of a workflow step +// WorkflowStepStatus record the status of a workflow step, include step status and subStep status +type WorkflowStepStatus struct { + StepStatus `json:",inline"` + SubStepsStatus []WorkflowSubStepStatus `json:"subSteps,omitempty"` +} + +// WorkflowSubStepStatus record the status of a workflow subStep type WorkflowSubStepStatus struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Phase WorkflowStepPhase `json:"phase,omitempty"` - // A human readable message indicating details about why the workflowStep is in this state. - Message string `json:"message,omitempty"` - // A brief CamelCase message indicating details about why the workflowStep is in this state. - Reason string `json:"reason,omitempty"` + StepStatus `json:",inline"` } // AppStatus defines the observed state of Application @@ -347,6 +345,25 @@ type WorkflowStep struct { // +kubebuilder:pruning:PreserveUnknownFields Properties *runtime.RawExtension `json:"properties,omitempty"` + SubSteps []WorkflowSubStep `json:"subSteps,omitempty"` + + DependsOn []string `json:"dependsOn,omitempty"` + + Inputs StepInputs `json:"inputs,omitempty"` + + Outputs StepOutputs `json:"outputs,omitempty"` +} + +// WorkflowSubStep defines how to execute a workflow subStep. +type WorkflowSubStep struct { + // Name is the unique name of the workflow step. + Name string `json:"name"` + + Type string `json:"type"` + + // +kubebuilder:pruning:PreserveUnknownFields + Properties *runtime.RawExtension `json:"properties,omitempty"` + DependsOn []string `json:"dependsOn,omitempty"` Inputs StepInputs `json:"inputs,omitempty"` @@ -372,13 +389,6 @@ type WorkflowStatus struct { StartTime metav1.Time `json:"startTime,omitempty"` } -// SubStepsStatus record the status of workflow steps. -type SubStepsStatus struct { - StepIndex int `json:"stepIndex,omitempty"` - Mode WorkflowMode `json:"mode,omitempty"` - Steps []WorkflowSubStepStatus `json:"steps,omitempty"` -} - // WorkflowStepPhase describes the phase of a workflow step. type WorkflowStepPhase string diff --git a/apis/core.oam.dev/common/zz_generated.deepcopy.go b/apis/core.oam.dev/common/zz_generated.deepcopy.go index fec9023f25924133158cf6ec7c725063b172a73f..cfeb8347920a7ed7a0d2285c17cd487d4fc355b9 100644 --- a/apis/core.oam.dev/common/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/common/zz_generated.deepcopy.go @@ -612,21 +612,18 @@ func (in StepOutputs) DeepCopy() StepOutputs { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SubStepsStatus) DeepCopyInto(out *SubStepsStatus) { +func (in *StepStatus) DeepCopyInto(out *StepStatus) { *out = *in - if in.Steps != nil { - in, out := &in.Steps, &out.Steps - *out = make([]WorkflowSubStepStatus, len(*in)) - copy(*out, *in) - } + in.FirstExecuteTime.DeepCopyInto(&out.FirstExecuteTime) + in.LastExecuteTime.DeepCopyInto(&out.LastExecuteTime) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubStepsStatus. -func (in *SubStepsStatus) DeepCopy() *SubStepsStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepStatus. +func (in *StepStatus) DeepCopy() *StepStatus { if in == nil { return nil } - out := new(SubStepsStatus) + out := new(StepStatus) in.DeepCopyInto(out) return out } @@ -692,6 +689,13 @@ func (in *WorkflowStep) DeepCopyInto(out *WorkflowStep) { *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } + if in.SubSteps != nil { + in, out := &in.SubSteps, &out.SubSteps + *out = make([]WorkflowSubStep, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.DependsOn != nil { in, out := &in.DependsOn, &out.DependsOn *out = make([]string, len(*in)) @@ -722,13 +726,14 @@ func (in *WorkflowStep) DeepCopy() *WorkflowStep { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkflowStepStatus) DeepCopyInto(out *WorkflowStepStatus) { *out = *in - if in.SubSteps != nil { - in, out := &in.SubSteps, &out.SubSteps - *out = new(SubStepsStatus) - (*in).DeepCopyInto(*out) + in.StepStatus.DeepCopyInto(&out.StepStatus) + if in.SubStepsStatus != nil { + in, out := &in.SubStepsStatus, &out.SubStepsStatus + *out = make([]WorkflowSubStepStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - in.FirstExecuteTime.DeepCopyInto(&out.FirstExecuteTime) - in.LastExecuteTime.DeepCopyInto(&out.LastExecuteTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowStepStatus. @@ -741,9 +746,45 @@ func (in *WorkflowStepStatus) DeepCopy() *WorkflowStepStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowSubStep) DeepCopyInto(out *WorkflowSubStep) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.DependsOn != nil { + in, out := &in.DependsOn, &out.DependsOn + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Inputs != nil { + in, out := &in.Inputs, &out.Inputs + *out = make(StepInputs, len(*in)) + copy(*out, *in) + } + if in.Outputs != nil { + in, out := &in.Outputs, &out.Outputs + *out = make(StepOutputs, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowSubStep. +func (in *WorkflowSubStep) DeepCopy() *WorkflowSubStep { + if in == nil { + return nil + } + out := new(WorkflowSubStep) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkflowSubStepStatus) DeepCopyInto(out *WorkflowSubStepStatus) { *out = *in + in.StepStatus.DeepCopyInto(&out.StepStatus) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowSubStepStatus. diff --git a/apis/core.oam.dev/v1beta1/zz_generated.deepcopy.go b/apis/core.oam.dev/v1beta1/zz_generated.deepcopy.go index 4bba8da4a5c924cda3ccdce26e2beda10b388852..acb2d5211b34b59252e36d390e68f669683c18b6 100644 --- a/apis/core.oam.dev/v1beta1/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/v1beta1/zz_generated.deepcopy.go @@ -954,6 +954,13 @@ func (in *WorkflowStep) DeepCopyInto(out *WorkflowStep) { *out = new(runtime.RawExtension) (*in).DeepCopyInto(*out) } + if in.SubSteps != nil { + in, out := &in.SubSteps, &out.SubSteps + *out = make([]common.WorkflowSubStep, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.DependsOn != nil { in, out := &in.DependsOn, &out.DependsOn *out = make([]string, len(*in)) diff --git a/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml b/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml index b99b42a1260df0ea9bb789660c6b8b56935c3dab..cfbeb0b7243a5b63df901fb9474b56409f5d17e3 100644 --- a/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml +++ b/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml @@ -856,7 +856,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of - a workflow step + a workflow step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time @@ -887,45 +887,44 @@ spec: state. type: string subSteps: - description: SubStepsStatus record the status of - workflow steps. - properties: - mode: - description: WorkflowMode describes the mode - of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record - the status of a workflow step - properties: - id: - type: string - message: - description: A human readable message - indicating details about why the workflowStep - is in this state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes - the phase of a workflow step. - type: string - reason: - description: A brief CamelCase message - indicating details about why the workflowStep - is in this state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the + status of a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first + time this step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time + this step execution. + format: date-time + type: string + message: + description: A human readable message indicating + details about why the workflowStep is in + this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the + phase of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in + this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -2245,6 +2244,57 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute + a workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input + of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the + workflow step. + type: string + outputs: + description: StepOutputs defines output variable + of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -2667,7 +2717,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of - a workflow step + a workflow step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time @@ -2698,45 +2748,44 @@ spec: state. type: string subSteps: - description: SubStepsStatus record the status of - workflow steps. - properties: - mode: - description: WorkflowMode describes the mode - of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record - the status of a workflow step - properties: - id: - type: string - message: - description: A human readable message - indicating details about why the workflowStep - is in this state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes - the phase of a workflow step. - type: string - reason: - description: A brief CamelCase message - indicating details about why the workflowStep - is in this state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the + status of a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first + time this step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time + this step execution. + format: date-time + type: string + message: + description: A human readable message indicating + details about why the workflowStep is in + this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the + phase of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in + this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -3937,6 +3986,57 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute a + workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of + WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow + step. + type: string + outputs: + description: StepOutputs defines output variable of + WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -4619,7 +4719,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -4648,44 +4748,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: diff --git a/charts/vela-core/crds/core.oam.dev_applications.yaml b/charts/vela-core/crds/core.oam.dev_applications.yaml index bd9ea725a40519d2a3912297ba3dc46e3db57d7d..c63283bc99768d525ae3b291804414b6789671ad 100644 --- a/charts/vela-core/crds/core.oam.dev_applications.yaml +++ b/charts/vela-core/crds/core.oam.dev_applications.yaml @@ -779,7 +779,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -808,44 +808,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -1054,6 +1052,57 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute a + workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of + WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow + step. + type: string + outputs: + description: StepOutputs defines output variable of + WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -1446,7 +1495,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -1475,44 +1524,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: diff --git a/charts/vela-core/crds/core.oam.dev_workflows.yaml b/charts/vela-core/crds/core.oam.dev_workflows.yaml index b0413fc5127d6aef1436101ec888b1b9631e5b94..155367c3ddfa165abfd7911a8995a1a6a923105e 100644 --- a/charts/vela-core/crds/core.oam.dev_workflows.yaml +++ b/charts/vela-core/crds/core.oam.dev_workflows.yaml @@ -74,6 +74,54 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute a workflow + subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow step. + type: string + outputs: + description: StepOutputs defines output variable of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -131,6 +179,54 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute a workflow + subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow step. + type: string + outputs: + description: StepOutputs defines output variable of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: diff --git a/charts/vela-core/templates/defwithtemplate/step-group.yaml b/charts/vela-core/templates/defwithtemplate/step-group.yaml new file mode 100644 index 0000000000000000000000000000000000000000..91768971ea8d4b1255534d16add56891f8af3599 --- /dev/null +++ b/charts/vela-core/templates/defwithtemplate/step-group.yaml @@ -0,0 +1,16 @@ +# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file. +# Definition source cue file: vela-templates/definitions/internal/step-group.cue +apiVersion: core.oam.dev/v1beta1 +kind: WorkflowStepDefinition +metadata: + annotations: + definition.oam.dev/description: step group + name: step-group + namespace: {{ include "systemDefinitionNamespace" . }} +spec: + schematic: + cue: + template: | + // no parameters + parameter: {} + diff --git a/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml b/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml index b99b42a1260df0ea9bb789660c6b8b56935c3dab..cfbeb0b7243a5b63df901fb9474b56409f5d17e3 100644 --- a/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml @@ -856,7 +856,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of - a workflow step + a workflow step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time @@ -887,45 +887,44 @@ spec: state. type: string subSteps: - description: SubStepsStatus record the status of - workflow steps. - properties: - mode: - description: WorkflowMode describes the mode - of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record - the status of a workflow step - properties: - id: - type: string - message: - description: A human readable message - indicating details about why the workflowStep - is in this state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes - the phase of a workflow step. - type: string - reason: - description: A brief CamelCase message - indicating details about why the workflowStep - is in this state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the + status of a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first + time this step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time + this step execution. + format: date-time + type: string + message: + description: A human readable message indicating + details about why the workflowStep is in + this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the + phase of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in + this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -2245,6 +2244,57 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute + a workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input + of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the + workflow step. + type: string + outputs: + description: StepOutputs defines output variable + of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -2667,7 +2717,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of - a workflow step + a workflow step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time @@ -2698,45 +2748,44 @@ spec: state. type: string subSteps: - description: SubStepsStatus record the status of - workflow steps. - properties: - mode: - description: WorkflowMode describes the mode - of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record - the status of a workflow step - properties: - id: - type: string - message: - description: A human readable message - indicating details about why the workflowStep - is in this state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes - the phase of a workflow step. - type: string - reason: - description: A brief CamelCase message - indicating details about why the workflowStep - is in this state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the + status of a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first + time this step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time + this step execution. + format: date-time + type: string + message: + description: A human readable message indicating + details about why the workflowStep is in + this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the + phase of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in + this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -3937,6 +3986,57 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute a + workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of + WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow + step. + type: string + outputs: + description: StepOutputs defines output variable of + WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -4619,7 +4719,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -4648,44 +4748,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: diff --git a/charts/vela-minimal/crds/core.oam.dev_applications.yaml b/charts/vela-minimal/crds/core.oam.dev_applications.yaml index bd9ea725a40519d2a3912297ba3dc46e3db57d7d..c63283bc99768d525ae3b291804414b6789671ad 100644 --- a/charts/vela-minimal/crds/core.oam.dev_applications.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_applications.yaml @@ -779,7 +779,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -808,44 +808,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -1054,6 +1052,57 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true + subSteps: + items: + description: WorkflowSubStep defines how to execute a + workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of + WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow + step. + type: string + outputs: + description: StepOutputs defines output variable of + WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -1446,7 +1495,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -1475,44 +1524,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: diff --git a/charts/vela-minimal/templates/defwithtemplate/step-group.yaml b/charts/vela-minimal/templates/defwithtemplate/step-group.yaml new file mode 100644 index 0000000000000000000000000000000000000000..91768971ea8d4b1255534d16add56891f8af3599 --- /dev/null +++ b/charts/vela-minimal/templates/defwithtemplate/step-group.yaml @@ -0,0 +1,16 @@ +# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file. +# Definition source cue file: vela-templates/definitions/internal/step-group.cue +apiVersion: core.oam.dev/v1beta1 +kind: WorkflowStepDefinition +metadata: + annotations: + definition.oam.dev/description: step group + name: step-group + namespace: {{ include "systemDefinitionNamespace" . }} +spec: + schematic: + cue: + template: | + // no parameters + parameter: {} + diff --git a/design/vela-core/workflow_policy.md b/design/vela-core/workflow_policy.md index de4a5d60298f95897c08247b4ee8d37a5110cfcc..57c7dca106cf8710216c4fd07cc2164b7c8c28f7 100644 --- a/design/vela-core/workflow_policy.md +++ b/design/vela-core/workflow_policy.md @@ -652,7 +652,7 @@ The process goes as: - The HelmTemplate/KustomizePatch controller would read the template from specified source, render the final config. It will compare the config with the Application object -- if there is difference, it will write back to the Application object per se. - The update of Application will trigger another event, the app controller will apply the HelmTemplate/KustomizePatch objects with new context. But this time, the HelmTemplate/KustomizePatch controller will find no diff after the rendering. So it will skip this time. -### Case 5: Conditional Check +### Case 6: Conditional Check In this case, users want to execute different steps based on the responseCode. When the `if` condition is not met, the step will be skipped. @@ -684,6 +684,28 @@ steps: type: notification ``` +### Case 7: step group + +In this case, the user runs multiple workflow steps in the `step-group` workflow type. subSteps in a step group will be executed in dag mode. +```yaml +workflow: + steps: + - type: step-group + name: run-step-group1 + subSteps: + - name: sub-step1 + type: ... + ... + - name: sub-step2 + type: ... + ... + +``` + +The process is as follows: + +- When executing a `step-group` step, the subSteps in the step group are executed in dag mode. A step group will only complete when all subSteps have been executed to completion. + ## Considerations ### Comparison with Argo Workflow/Tekton diff --git a/docs/examples/workflow/step-group/app.yaml b/docs/examples/workflow/step-group/app.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0e47d6f07a250087a33367df399904fd1789b8ca --- /dev/null +++ b/docs/examples/workflow/step-group/app.yaml @@ -0,0 +1,22 @@ +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: step-group-example + namespace: default +spec: + components: + - name: express-server + type: webservice + properties: + image: crccheck/hello-world + port: 8000 + + workflow: + steps: + - name: step + type: step-group + subSteps: + - name: apply-server + type: apply-component + properties: + component: express-server diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml index 22f8d1223dd663f05df51d1f5571907b59bb70a4..f0f4d208553279e79b0423ced380c9649ab77c0a 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applicationrevisions.yaml @@ -856,7 +856,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of - a workflow step + a workflow step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time @@ -887,45 +887,44 @@ spec: state. type: string subSteps: - description: SubStepsStatus record the status of - workflow steps. - properties: - mode: - description: WorkflowMode describes the mode - of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record - the status of a workflow step - properties: - id: - type: string - message: - description: A human readable message - indicating details about why the workflowStep - is in this state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes - the phase of a workflow step. - type: string - reason: - description: A brief CamelCase message - indicating details about why the workflowStep - is in this state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the + status of a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first + time this step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time + this step execution. + format: date-time + type: string + message: + description: A human readable message indicating + details about why the workflowStep is in + this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the + phase of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in + this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -2245,6 +2244,57 @@ spec: properties: type: object + subSteps: + items: + description: WorkflowSubStep defines how to execute + a workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input + of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the + workflow step. + type: string + outputs: + description: StepOutputs defines output variable + of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -2667,7 +2717,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of - a workflow step + a workflow step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time @@ -2698,45 +2748,44 @@ spec: state. type: string subSteps: - description: SubStepsStatus record the status of - workflow steps. - properties: - mode: - description: WorkflowMode describes the mode - of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record - the status of a workflow step - properties: - id: - type: string - message: - description: A human readable message - indicating details about why the workflowStep - is in this state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes - the phase of a workflow step. - type: string - reason: - description: A brief CamelCase message - indicating details about why the workflowStep - is in this state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the + status of a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first + time this step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time + this step execution. + format: date-time + type: string + message: + description: A human readable message indicating + details about why the workflowStep is in + this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the + phase of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in + this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -3937,6 +3986,57 @@ spec: properties: type: object + subSteps: + items: + description: WorkflowSubStep defines how to execute a + workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of + WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow + step. + type: string + outputs: + description: StepOutputs defines output variable of + WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -4619,7 +4719,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -4648,44 +4748,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml index d003fb58203a5335fd23a402667625ffbc8e1c7a..8a398661ace1fa1f2694218306b803ff352f7458 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_applications.yaml @@ -780,7 +780,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -809,44 +809,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: @@ -1055,6 +1053,57 @@ spec: properties: type: object + subSteps: + items: + description: WorkflowSubStep defines how to execute a + workflow subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of + WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow + step. + type: string + outputs: + description: StepOutputs defines output variable of + WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -1447,7 +1496,7 @@ spec: steps: items: description: WorkflowStepStatus record the status of a workflow - step + step, include step status and subStep status properties: firstExecuteTime: description: FirstExecuteTime is the first time this step @@ -1476,44 +1525,42 @@ spec: about why the workflowStep is in this state. type: string subSteps: - description: SubStepsStatus record the status of workflow - steps. - properties: - mode: - description: WorkflowMode describes the mode of workflow - type: string - stepIndex: - type: integer - steps: - items: - description: WorkflowSubStepStatus record the status - of a workflow step - properties: - id: - type: string - message: - description: A human readable message indicating - details about why the workflowStep is in this - state. - type: string - name: - type: string - phase: - description: WorkflowStepPhase describes the phase - of a workflow step. - type: string - reason: - description: A brief CamelCase message indicating - details about why the workflowStep is in this - state. - type: string - type: - type: string - required: - - id - type: object - type: array - type: object + items: + description: WorkflowSubStepStatus record the status of + a workflow subStep + properties: + firstExecuteTime: + description: FirstExecuteTime is the first time this + step execution. + format: date-time + type: string + id: + type: string + lastExecuteTime: + description: LastExecuteTime is the last time this + step execution. + format: date-time + type: string + message: + description: A human readable message indicating details + about why the workflowStep is in this state. + type: string + name: + type: string + phase: + description: WorkflowStepPhase describes the phase + of a workflow step. + type: string + reason: + description: A brief CamelCase message indicating + details about why the workflowStep is in this state. + type: string + type: + type: string + required: + - id + type: object + type: array type: type: string required: diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workflows.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workflows.yaml index ef80e8f57968a08de1c4d76f0644192afb67c9c8..0f0b9a14f5600b70bf53335af4e59f9d5cbd2165 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_workflows.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_workflows.yaml @@ -74,6 +74,54 @@ spec: properties: type: object + subSteps: + items: + description: WorkflowSubStep defines how to execute a workflow + subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow step. + type: string + outputs: + description: StepOutputs defines output variable of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: @@ -131,6 +179,54 @@ spec: properties: type: object + subSteps: + items: + description: WorkflowSubStep defines how to execute a workflow + subStep. + properties: + dependsOn: + items: + type: string + type: array + inputs: + description: StepInputs defines variable input of WorkflowStep + items: + properties: + from: + type: string + parameterKey: + type: string + required: + - from + - parameterKey + type: object + type: array + name: + description: Name is the unique name of the workflow step. + type: string + outputs: + description: StepOutputs defines output variable of WorkflowStep + items: + properties: + name: + type: string + valueFrom: + type: string + required: + - name + - valueFrom + type: object + type: array + properties: + type: object + + type: + type: string + required: + - name + - type + type: object + type: array type: type: string required: diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index 283bca8d6c9553afc3ae8b9ee69d413293a5c582..c51f46ca2e167e2f85e2a54c1e122ba25e675ff7 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -444,21 +444,37 @@ func (p *Parser) parseWorkflowSteps(ctx context.Context, af *Appfile) error { return err } for _, workflowStep := range af.WorkflowSteps { - if wftypes.IsBuiltinWorkflowStepType(workflowStep.Type) { - continue - } - if _, found := af.RelatedWorkflowStepDefinitions[workflowStep.Type]; found { - continue + err := p.parseWorkflowStep(ctx, af, workflowStep.Type) + if err != nil { + return err } - def := &v1beta1.WorkflowStepDefinition{} - if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStep.Type); err != nil { - return errors.Wrapf(err, "failed to get workflow step definition %s", workflowStep.Type) + + if workflowStep.SubSteps != nil { + for _, workflowSubStep := range workflowStep.SubSteps { + err := p.parseWorkflowStep(ctx, af, workflowSubStep.Type) + if err != nil { + return err + } + } } - af.RelatedWorkflowStepDefinitions[workflowStep.Type] = def } return nil } +func (p *Parser) parseWorkflowStep(ctx context.Context, af *Appfile, workflowStepType string) error { + if wftypes.IsBuiltinWorkflowStepType(workflowStepType) { + return nil + } + if _, found := af.RelatedWorkflowStepDefinitions[workflowStepType]; found { + return nil + } + def := &v1beta1.WorkflowStepDefinition{} + if err := util.GetCapabilityDefinition(ctx, p.client, def, workflowStepType); err != nil { + return errors.Wrapf(err, "failed to get workflow step definition %s", workflowStepType) + } + af.RelatedWorkflowStepDefinitions[workflowStepType] = def + return nil +} func (p *Parser) makeWorkload(ctx context.Context, name, typ string, capType types.CapType, props *runtime.RawExtension) (*Workload, error) { templ, err := p.tmplLoader.LoadTemplate(ctx, p.dm, p.client, typ, capType) if err != nil { diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/generator.go b/pkg/controller/core.oam.dev/v1alpha2/application/generator.go index e595186823728ddb801ed4bca72358e3935e1966..00e21ae59b973f4ca567e4e6ea8e2448193ec62b 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/generator.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/generator.go @@ -83,27 +83,7 @@ func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context, var tasks []wfTypes.TaskRunner for _, step := range af.WorkflowSteps { - options := &wfTypes.GeneratorOptions{ - ID: generateStepID(step.Name, app.Status.Workflow), - } - generatorName := step.Type - if generatorName == wfTypes.WorkflowStepTypeApplyComponent { - generatorName = wfTypes.WorkflowStepTypeBuiltinApplyComponent - options.StepConvertor = func(lstep v1beta1.WorkflowStep) (v1beta1.WorkflowStep, error) { - copierStep := lstep.DeepCopy() - if err := convertStepProperties(copierStep, app); err != nil { - return lstep, errors.WithMessage(err, "convert [apply-component]") - } - return *copierStep, nil - } - } - - genTask, err := taskDiscover.GetTaskGenerator(ctx, generatorName) - if err != nil { - return nil, err - } - - task, err := genTask(step, options) + task, err := generateStep(ctx, app, step, taskDiscover, "") if err != nil { return nil, err } @@ -112,6 +92,57 @@ func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context, return tasks, nil } +func generateStep(ctx context.Context, + app *v1beta1.Application, + step v1beta1.WorkflowStep, + taskDiscover wfTypes.TaskDiscover, + parentStepName string) (wfTypes.TaskRunner, error) { + options := &wfTypes.GeneratorOptions{ + ID: generateStepID(step.Name, app.Status.Workflow, parentStepName), + } + generatorName := step.Type + switch { + case generatorName == wfTypes.WorkflowStepTypeApplyComponent: + generatorName = wfTypes.WorkflowStepTypeBuiltinApplyComponent + options.StepConvertor = func(lstep v1beta1.WorkflowStep) (v1beta1.WorkflowStep, error) { + copierStep := lstep.DeepCopy() + if err := convertStepProperties(copierStep, app); err != nil { + return lstep, errors.WithMessage(err, "convert [apply-component]") + } + return *copierStep, nil + } + case generatorName == wfTypes.WorkflowStepTypeStepGroup: + var subTaskRunners []wfTypes.TaskRunner + for _, subStep := range step.SubSteps { + workflowStep := v1beta1.WorkflowStep{ + Name: subStep.Name, + Type: subStep.Type, + Properties: subStep.Properties, + DependsOn: subStep.DependsOn, + Inputs: subStep.Inputs, + Outputs: subStep.Outputs, + } + subTask, err := generateStep(ctx, app, workflowStep, taskDiscover, step.Name) + if err != nil { + return nil, err + } + subTaskRunners = append(subTaskRunners, subTask) + } + options.SubTaskRunners = subTaskRunners + } + + genTask, err := taskDiscover.GetTaskGenerator(ctx, generatorName) + if err != nil { + return nil, err + } + + task, err := genTask(step, options) + if err != nil { + return nil, err + } + return task, nil +} + func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application) error { o := struct { Component string `json:"component"` @@ -360,15 +391,38 @@ func getComponentResources(ctx context.Context, manifest *types.ComponentManifes return workload, traits, nil } -func generateStepID(stepName string, wfStatus *common.WorkflowStatus) string { +func getStepID(stepName string, stepsStatus []common.StepStatus) string { + for _, status := range stepsStatus { + if status.Name == stepName { + return status.ID + } + } + return "" +} + +func generateStepID(stepName string, workflowStatus *common.WorkflowStatus, parentStepName string) string { var id string - if wfStatus != nil { - for _, status := range wfStatus.Steps { - if status.Name == stepName { - id = status.ID + if workflowStatus != nil { + workflowStepsStatus := workflowStatus.Steps + if parentStepName != "" { + for _, status := range workflowStepsStatus { + if status.Name == parentStepName { + var stepsStatus []common.StepStatus + for _, status := range status.SubStepsStatus { + stepsStatus = append(stepsStatus, status.StepStatus) + } + id = getStepID(stepName, stepsStatus) + } } + } else { + var stepsStatus []common.StepStatus + for _, status := range workflowStepsStatus { + stepsStatus = append(stepsStatus, status.StepStatus) + } + id = getStepID(stepName, stepsStatus) } } + if id == "" { id = utils.RandomString(10) } diff --git a/pkg/workflow/tasks/custom/task.go b/pkg/workflow/tasks/custom/task.go index cc3af3535ed494d531885388bfc8efa358eb8351..a25e5dccee2523aa43b4c136bc221d7f4ce3069a 100644 --- a/pkg/workflow/tasks/custom/task.go +++ b/pkg/workflow/tasks/custom/task.go @@ -84,7 +84,7 @@ func (t *TaskLoader) GetTaskGenerator(ctx context.Context, name string) (wfTypes type taskRunner struct { name string - run func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) + run func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) checkPending func(ctx wfContext.Context) bool } @@ -94,7 +94,7 @@ func (tr *taskRunner) Name() string { } // Run execute task. -func (tr *taskRunner) Run(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { +func (tr *taskRunner) Run(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { return tr.run(ctx, options) } @@ -103,12 +103,17 @@ func (tr *taskRunner) Pending(ctx wfContext.Context) bool { return tr.checkPending(ctx) } +// SubTaskRunners return child step names. it could be null if the step have no sub-step +func (tr *taskRunner) SubTaskRunners() []wfTypes.TaskRunner { + return nil +} + func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, error) { return func(wfStep v1beta1.WorkflowStep, genOpt *wfTypes.GeneratorOptions) (wfTypes.TaskRunner, error) { exec := &executor{ handlers: t.handlers, - wfStatus: common.WorkflowStepStatus{ + wfStatus: common.StepStatus{ Name: wfStep.Name, Type: wfStep.Type, Phase: common.WorkflowStepPhaseSucceeded, @@ -154,7 +159,7 @@ func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, err } return false } - tRunner.run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { + tRunner.run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { if options.GetTracer == nil { options.GetTracer = func(id string, step v1beta1.WorkflowStep) monitorContext.Context { return monitorContext.NewTraceContext(context.Background(), "") @@ -177,13 +182,13 @@ func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, err paramsValue, err := ctx.MakeParameter(params) if err != nil { tracer.Error(err, "make parameter") - return common.WorkflowStepStatus{}, nil, errors.WithMessage(err, "make parameter") + return common.StepStatus{}, nil, errors.WithMessage(err, "make parameter") } for _, hook := range options.PreStartHooks { if err := hook(ctx, paramsValue, wfStep); err != nil { tracer.Error(err, "do preStartHook") - return common.WorkflowStepStatus{}, nil, errors.WithMessage(err, "do preStartHook") + return common.StepStatus{}, nil, errors.WithMessage(err, "do preStartHook") } } @@ -196,7 +201,7 @@ func (t *TaskLoader) makeTaskGenerator(templ string) (wfTypes.TaskGenerator, err if params != nil { ps, err := paramsValue.String() if err != nil { - return common.WorkflowStepStatus{}, nil, errors.WithMessage(err, "params encode") + return common.StepStatus{}, nil, errors.WithMessage(err, "params encode") } paramFile = fmt.Sprintf(model.ParameterFieldName+": {%s}\n", ps) } @@ -256,7 +261,7 @@ func (t *TaskLoader) makeValue(ctx wfContext.Context, templ string, id string, p type executor struct { handlers providers.Providers - wfStatus common.WorkflowStepStatus + wfStatus common.StepStatus suspend bool terminated bool failedAfterRetries bool @@ -314,7 +319,7 @@ func (exec *executor) operation() *wfTypes.Operation { } } -func (exec *executor) status() common.WorkflowStepStatus { +func (exec *executor) status() common.StepStatus { return exec.wfStatus } diff --git a/pkg/workflow/tasks/discover.go b/pkg/workflow/tasks/discover.go index 98cb4dfe93aeda0b3b41551e978eb3a264aeb8ac..05a5a26f6bb6183f24044163d4e369177d46a43a 100644 --- a/pkg/workflow/tasks/discover.go +++ b/pkg/workflow/tasks/discover.go @@ -87,6 +87,14 @@ func suspend(step v1beta1.WorkflowStep, opt *types.GeneratorOptions) (types.Task return tr, nil } +func stepGroup(step v1beta1.WorkflowStep, opt *types.GeneratorOptions) (types.TaskRunner, error) { + return &stepGroupTaskRunner{ + id: opt.ID, + name: step.Name, + subTaskRunners: opt.SubTaskRunners, + }, nil +} + func newTaskDiscover(ctx monitorContext.Context, providerHandlers providers.Providers, pd *packages.PackageDiscover, pCtx process.Context, templateLoader template.Loader) types.TaskDiscover { // install builtin provider workspace.Install(providerHandlers) @@ -95,7 +103,8 @@ func newTaskDiscover(ctx monitorContext.Context, providerHandlers providers.Prov return &taskDiscover{ builtins: map[string]types.TaskGenerator{ - types.WorkflowStepTypeSuspend: suspend, + types.WorkflowStepTypeSuspend: suspend, + types.WorkflowStepTypeStepGroup: stepGroup, }, remoteTaskDiscover: custom.NewTaskLoader(templateLoader.LoadTaskTemplate, pd, providerHandlers, 0, pCtx), templateLoader: templateLoader, @@ -120,8 +129,8 @@ func (tr *suspendTaskRunner) Name() string { } // Run make workflow suspend. -func (tr *suspendTaskRunner) Run(ctx wfContext.Context, options *types.TaskRunOptions) (common.WorkflowStepStatus, *types.Operation, error) { - stepStatus := common.WorkflowStepStatus{ +func (tr *suspendTaskRunner) Run(ctx wfContext.Context, options *types.TaskRunOptions) (common.StepStatus, *types.Operation, error) { + stepStatus := common.StepStatus{ ID: tr.id, Name: tr.name, Type: types.WorkflowStepTypeSuspend, @@ -140,6 +149,46 @@ func (tr *suspendTaskRunner) Pending(ctx wfContext.Context) bool { return false } +// SubTaskRunners return child step names. it could be null if the step have no sub-step +func (tr *suspendTaskRunner) SubTaskRunners() []types.TaskRunner { + return nil +} + +type stepGroupTaskRunner struct { + id string + name string + subTaskRunners []types.TaskRunner +} + +// Name return suspend step name. +func (tr *stepGroupTaskRunner) Name() string { + return tr.name +} + +// SubTaskRunners return child step runners. it could be null if the step have no sub-step +func (tr *stepGroupTaskRunner) SubTaskRunners() []types.TaskRunner { + return tr.subTaskRunners +} + +// Run make workflow step group. +func (tr *stepGroupTaskRunner) Run(ctx wfContext.Context, options *types.TaskRunOptions) (common.StepStatus, *types.Operation, error) { + phase := common.WorkflowStepPhaseRunning + if tr.subTaskRunners == nil { + phase = common.WorkflowStepPhaseSucceeded + } + return common.StepStatus{ + ID: tr.id, + Name: tr.name, + Type: types.WorkflowStepTypeStepGroup, + Phase: phase, + }, &types.Operation{}, nil +} + +// Pending check task should be executed or not. +func (tr *stepGroupTaskRunner) Pending(ctx wfContext.Context) bool { + return false +} + // NewViewTaskDiscover will create a client for load task generator. func NewViewTaskDiscover(pd *packages.PackageDiscover, cli client.Client, cfg *rest.Config, apply kube.Dispatcher, delete kube.Deleter, viewNs string, logLevel int, pCtx process.Context) types.TaskDiscover { handlerProviders := providers.NewProviders() diff --git a/pkg/workflow/tasks/discover_test.go b/pkg/workflow/tasks/discover_test.go index 7e8d170871581189a31ca248d5f706156763187f..f36312e08ccc6453e88925ffd0402d14b86b7415 100644 --- a/pkg/workflow/tasks/discover_test.go +++ b/pkg/workflow/tasks/discover_test.go @@ -55,13 +55,16 @@ func TestDiscover(t *testing.T) { }) discover := &taskDiscover{ builtins: map[string]types.TaskGenerator{ - "suspend": suspend, + "suspend": suspend, + "stepGroup": stepGroup, }, remoteTaskDiscover: custom.NewTaskLoader(loadTemplate, nil, nil, 0, pCtx), } _, err := discover.GetTaskGenerator(context.Background(), "suspend") assert.NilError(t, err) + _, err = discover.GetTaskGenerator(context.Background(), "stepGroup") + assert.NilError(t, err) _, err = discover.GetTaskGenerator(context.Background(), "foo") assert.NilError(t, err) _, err = discover.GetTaskGenerator(context.Background(), "crazy") @@ -90,3 +93,23 @@ func TestSuspendStep(t *testing.T) { assert.Equal(t, status.Name, "test") assert.Equal(t, status.Phase, common.WorkflowStepPhaseSucceeded) } + +func TestStepGroupStep(t *testing.T) { + discover := &taskDiscover{ + builtins: map[string]types.TaskGenerator{ + "stepGroup": stepGroup, + }, + } + gen, err := discover.GetTaskGenerator(context.Background(), "stepGroup") + assert.NilError(t, err) + runner, err := gen(v1beta1.WorkflowStep{Name: "test"}, &types.GeneratorOptions{ID: "124"}) + assert.NilError(t, err) + assert.Equal(t, runner.Name(), "test") + assert.Equal(t, runner.Pending(nil), false) + status, act, err := runner.Run(nil, nil) + assert.NilError(t, err) + assert.Equal(t, act.Suspend, false) + assert.Equal(t, status.ID, "124") + assert.Equal(t, status.Name, "test") + assert.Equal(t, status.Phase, common.WorkflowStepPhaseSucceeded) +} diff --git a/pkg/workflow/types/types.go b/pkg/workflow/types/types.go index ebcd38fff564a9529f8cc421b6fb8fe12316817f..349cb0b47aa3d34865e4401aecd2ffb764249a71 100644 --- a/pkg/workflow/types/types.go +++ b/pkg/workflow/types/types.go @@ -30,8 +30,9 @@ import ( // TaskRunner is a task runner. type TaskRunner interface { Name() string + SubTaskRunners() []TaskRunner Pending(ctx wfContext.Context) bool - Run(ctx wfContext.Context, options *TaskRunOptions) (common.WorkflowStepStatus, *Operation, error) + Run(ctx wfContext.Context, options *TaskRunOptions) (common.StepStatus, *Operation, error) } // TaskDiscover is the interface to obtain the TaskGenerator。 @@ -69,9 +70,10 @@ type TaskGenerator func(wfStep v1beta1.WorkflowStep, options *GeneratorOptions) // GeneratorOptions is the options for generate task. type GeneratorOptions struct { - ID string - PrePhase common.WorkflowStepPhase - StepConvertor func(step v1beta1.WorkflowStep) (v1beta1.WorkflowStep, error) + ID string + PrePhase common.WorkflowStepPhase + StepConvertor func(step v1beta1.WorkflowStep) (v1beta1.WorkflowStep, error) + SubTaskRunners []TaskRunner } // Action is that workflow provider can do. @@ -103,4 +105,6 @@ const ( WorkflowStepTypeApplyComponent = "apply-component" // WorkflowStepTypeBuiltinApplyComponent type builtin-apply-component WorkflowStepTypeBuiltinApplyComponent = "builtin-apply-component" + // WorkflowStepTypeStepGroup type step-group + WorkflowStepTypeStepGroup = "step-group" ) diff --git a/pkg/workflow/types/utils.go b/pkg/workflow/types/utils.go index 72ab8571b344734383922166c435c8e24d87a9ee..1a7a5e9d96eeb605960d4ce63ebca05fd1b4cd69 100644 --- a/pkg/workflow/types/utils.go +++ b/pkg/workflow/types/utils.go @@ -22,6 +22,7 @@ func IsBuiltinWorkflowStepType(wfType string) bool { WorkflowStepTypeSuspend, WorkflowStepTypeApplyComponent, WorkflowStepTypeBuiltinApplyComponent, + WorkflowStepTypeStepGroup, } { if _type == wfType { return true diff --git a/pkg/workflow/workflow.go b/pkg/workflow/workflow.go index 93a92a0791a0693f240421545919f08e751c1f85..f27549079665bc64e36660b6244228cfb3fc4786 100644 --- a/pkg/workflow/workflow.go +++ b/pkg/workflow/workflow.go @@ -351,22 +351,37 @@ func (w *workflow) setMetadataToContext(wfCtx wfContext.Context) error { return wfCtx.SetVar(metadata, wfTypes.ContextKeyMetadata) } +func (e *engine) getBackoffTimes(stepID string) (success bool, backoffTimes int) { + if v, ok := e.wfCtx.GetValueInMemory(wfTypes.ContextPrefixBackoffTimes, stepID); ok { + times, ok := v.(int) + if ok { + return true, times + } + } + return false, 0 +} + func (e *engine) getBackoffWaitTime() int { // the default value of min times reaches the max workflow backoff wait time minTimes := 15 found := false for _, step := range e.status.Steps { - if v, ok := e.wfCtx.GetValueInMemory(wfTypes.ContextPrefixBackoffTimes, step.ID); ok { + success, backoffTimes := e.getBackoffTimes(step.ID) + if success && backoffTimes < minTimes { + minTimes = backoffTimes found = true - times, ok := v.(int) - if !ok { - times = 0 - } - if times < minTimes { - minTimes = times + } + if step.SubStepsStatus != nil { + for _, subStep := range step.SubStepsStatus { + success, backoffTimes := e.getBackoffTimes(subStep.ID) + if success && backoffTimes < minTimes { + minTimes = backoffTimes + found = true + } } } } + if !found { return minWorkflowBackoffWaitTime } @@ -460,12 +475,16 @@ func (e *engine) runAsDAG(taskRunners []wfTypes.TaskRunner) error { } +func (e *engine) runAsStepByStep(taskRunners []wfTypes.TaskRunner) error { + return e.steps(e.todoByIndex(taskRunners)) +} + func (e *engine) run(taskRunners []wfTypes.TaskRunner) error { var err error if e.dagMode { err = e.runAsDAG(taskRunners) } else { - err = e.steps(e.todoByIndex(taskRunners)) + err = e.runAsStepByStep(taskRunners) } e.setNextExecuteTime() @@ -500,64 +519,135 @@ func (e *engine) todoByIndex(taskRunners []wfTypes.TaskRunner) []wfTypes.TaskRun } return taskRunners[index:] } - func (e *engine) steps(taskRunners []wfTypes.TaskRunner) error { - wfCtx := e.wfCtx for _, runner := range taskRunners { - options := &wfTypes.TaskRunOptions{ - GetTracer: func(id string, stepStatus oamcore.WorkflowStep) monitorContext.Context { - return e.monitorCtx.Fork(id, monitorContext.DurationMetric(func(v float64) { - metrics.StepDurationHistogram.WithLabelValues("application", stepStatus.Type).Observe(v) - })) - }, - } - if e.debug { - options.Debug = func(step string, v *value.Value) error { - debugContext := debug.NewContext(e.cli, e.rk, e.app, step) - if err := debugContext.Set(v); err != nil { - return err - } - return nil - } + var err error + var needStop bool + if runner.SubTaskRunners() == nil { + needStop, err = e.step(runner, e.isDag(), "") + } else { + needStop, err = e.stepWithSubSteps(runner) } - status, operation, err := runner.Run(wfCtx, options) - if err != nil { + if needStop || err != nil { return err } + } + return nil +} - e.updateStepStatus(status) - - e.failedAfterRetries = e.failedAfterRetries || operation.FailedAfterRetries - e.waiting = e.waiting || operation.Waiting - if status.Phase == common.WorkflowStepPhaseSucceeded || (status.Phase == common.WorkflowStepPhaseRunning && status.Type == wfTypes.WorkflowStepTypeSuspend) { - wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID) - wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffReason, status.ID) - if err := wfCtx.Commit(); err != nil { - return errors.WithMessage(err, "commit workflow context") - } - - e.finishStep(operation) - if e.needStop() { - return nil +func (e *engine) step(runner wfTypes.TaskRunner, dag bool, parentStepName string) (bool, error) { + wfCtx := e.wfCtx + options := &wfTypes.TaskRunOptions{ + GetTracer: func(id string, stepStatus oamcore.WorkflowStep) monitorContext.Context { + return e.monitorCtx.Fork(id, monitorContext.DurationMetric(func(v float64) { + metrics.StepDurationHistogram.WithLabelValues("application", stepStatus.Type).Observe(v) + })) + }, + } + if e.debug { + options.Debug = func(step string, v *value.Value) error { + debugContext := debug.NewContext(e.cli, e.rk, e.app, step) + if err := debugContext.Set(v); err != nil { + return err } - continue + return nil } - - if val, exists := wfCtx.GetValueInMemory(wfTypes.ContextPrefixBackoffReason, status.ID); !exists || val != status.Message { - wfCtx.SetValueInMemory(status.Message, wfTypes.ContextPrefixBackoffReason, status.ID) - wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID) + } + status, operation, err := runner.Run(wfCtx, options) + if err != nil { + return true, err + } + if parentStepName != "" { + subStepStatus := common.WorkflowSubStepStatus{ + StepStatus: status, + } + e.updateSubStepStatus(parentStepName, subStepStatus) + } else { + workflowStepStatus := common.WorkflowStepStatus{ + StepStatus: status, } - wfCtx.IncreaseCountValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID) + e.updateStepStatus(workflowStepStatus) + } + + e.failedAfterRetries = e.failedAfterRetries || operation.FailedAfterRetries + e.waiting = e.waiting || operation.Waiting + if status.Phase == common.WorkflowStepPhaseSucceeded || (status.Phase == common.WorkflowStepPhaseRunning && status.Type == wfTypes.WorkflowStepTypeSuspend) { + wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID) + wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffReason, status.ID) if err := wfCtx.Commit(); err != nil { - return errors.WithMessage(err, "commit workflow context") + return true, errors.WithMessage(err, "commit workflow context") } - if e.isDag() { - continue + + e.finishStep(operation) + if e.needStop() { + return true, nil } - e.checkFailedAfterRetries() - return nil + return false, nil } - return nil + + if val, exists := wfCtx.GetValueInMemory(wfTypes.ContextPrefixBackoffReason, status.ID); !exists || val != status.Message { + wfCtx.SetValueInMemory(status.Message, wfTypes.ContextPrefixBackoffReason, status.ID) + wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID) + } + wfCtx.IncreaseCountValueInMemory(wfTypes.ContextPrefixBackoffTimes, status.ID) + if err := wfCtx.Commit(); err != nil { + return true, errors.WithMessage(err, "commit workflow context") + } + if dag { + return false, nil + } + e.checkFailedAfterRetries() + return true, nil +} + +func (e *engine) stepWithSubSteps(runner wfTypes.TaskRunner) (bool, error) { + stepName := runner.Name() + var err error + var needStop bool + var status common.StepStatus + status, _, err = runner.Run(e.wfCtx, &wfTypes.TaskRunOptions{}) + if err != nil { + return true, err + } + workflowStepStatus := common.WorkflowStepStatus{ + StepStatus: status, + SubStepsStatus: e.getStepStatus(stepName).SubStepsStatus, + } + e.updateStepStatus(workflowStepStatus) + + todoSubTaskRunners, pendingSubTaskRunners := e.getTodoSubTaskRunners(runner) + for _, todoSubTaskRunner := range todoSubTaskRunners { + // subStep only use dag mode + dag := true + needStop, err = e.step(todoSubTaskRunner, dag, stepName) + } + + stepStatus := e.getStepStatus(stepName) + subStepPhaseToCount := make(map[common.WorkflowStepPhase]int) + for _, subStepsStatus := range stepStatus.SubStepsStatus { + subStepPhaseToCount[subStepsStatus.Phase]++ + } + switch { + case subStepPhaseToCount[common.WorkflowStepPhaseRunning] != 0: + stepStatus.Phase = common.WorkflowStepPhaseRunning + case subStepPhaseToCount[common.WorkflowStepPhaseStopped] != 0: + stepStatus.Phase = common.WorkflowStepPhaseStopped + case subStepPhaseToCount[common.WorkflowStepPhaseFailed] != 0: + stepStatus.Phase = common.WorkflowStepPhaseFailed + case len(pendingSubTaskRunners) != 0: + stepStatus.Phase = common.WorkflowStepPhaseRunning + default: + stepStatus.Phase = common.WorkflowStepPhaseSucceeded + } + e.updateStepStatus(stepStatus) + e.checkFailedAfterRetries() + if needStop || err != nil { + return true, err + } + if stepStatus.Phase == common.WorkflowStepPhaseSucceeded { + return false, nil + } + return true, nil } type engine struct { @@ -585,25 +675,60 @@ func (e *engine) finishStep(operation *wfTypes.Operation) { } func (e *engine) updateStepStatus(status common.WorkflowStepStatus) { + var now = metav1.NewTime(time.Now()) + e.wfCtx.SetValueInMemory(now.Unix(), wfTypes.ContextKeyLastExecuteTime) + e.status.Steps = updateStepStatusByName(e.status.Steps, status, now) +} + +func updateStepStatusByName(stepStatus []common.WorkflowStepStatus, status common.WorkflowStepStatus, now metav1.Time) []common.WorkflowStepStatus { var ( conditionUpdated bool - now = metav1.NewTime(time.Now()) ) - e.wfCtx.SetValueInMemory(now.Unix(), wfTypes.ContextKeyLastExecuteTime) status.LastExecuteTime = now + for i := range stepStatus { + if stepStatus[i].Name == status.Name { + status.FirstExecuteTime = stepStatus[i].FirstExecuteTime + stepStatus[i] = status + conditionUpdated = true + break + } + } + if !conditionUpdated { + status.FirstExecuteTime = now + stepStatus = append(stepStatus, status) + } + return stepStatus +} + +func (e *engine) updateSubStepStatus(stepName string, subStepStatus common.WorkflowSubStepStatus) { + var now = metav1.NewTime(time.Now()) for i := range e.status.Steps { - if e.status.Steps[i].Name == status.Name { - status.FirstExecuteTime = e.status.Steps[i].FirstExecuteTime - e.status.Steps[i] = status + if e.status.Steps[i].Name == stepName { + e.status.Steps[i].SubStepsStatus = updateSubStepStatusByName(e.status.Steps[i].SubStepsStatus, subStepStatus, now) + } + } +} + +func updateSubStepStatusByName(stepStatus []common.WorkflowSubStepStatus, status common.WorkflowSubStepStatus, now metav1.Time) []common.WorkflowSubStepStatus { + var ( + conditionUpdated bool + ) + + status.LastExecuteTime = now + for i := range stepStatus { + if stepStatus[i].Name == status.Name { + status.FirstExecuteTime = stepStatus[i].FirstExecuteTime + stepStatus[i] = status conditionUpdated = true break } } if !conditionUpdated { status.FirstExecuteTime = now - e.status.Steps = append(e.status.Steps, status) + stepStatus = append(stepStatus, status) } + return stepStatus } func (e *engine) checkFailedAfterRetries() { @@ -637,3 +762,53 @@ func ComputeWorkflowRevisionHash(rev string, app *oamcore.Application) (string, func IsFailedAfterRetry(app *oamcore.Application) bool { return app.Status.Workflow != nil && app.Status.Workflow.Message == MessageFailedAfterRetries } + +func (e *engine) getTodoSubTaskRunners(taskRunner wfTypes.TaskRunner) ([]wfTypes.TaskRunner, []wfTypes.TaskRunner) { + wfCtx := e.wfCtx + var ( + todoSubTasks []wfTypes.TaskRunner + pendingSubTasks []wfTypes.TaskRunner + ) + for _, subTRunner := range taskRunner.SubTaskRunners() { + ready := false + var stepID string + subStepStatus := e.getSubStepStatus(taskRunner.Name(), subTRunner.Name()) + ready = subStepStatus.Phase == common.WorkflowStepPhaseSucceeded + stepID = subStepStatus.ID + + if !ready { + if subTRunner.Pending(wfCtx) { + pendingSubTasks = append(pendingSubTasks, subTRunner) + continue + } + todoSubTasks = append(todoSubTasks, subTRunner) + } else { + wfCtx.DeleteValueInMemory(wfTypes.ContextPrefixBackoffTimes, stepID) + } + } + return todoSubTasks, pendingSubTasks +} + +func (e *engine) getSubStepStatus(stepName string, subStepName string) common.WorkflowSubStepStatus { + // ss is step status + for _, ss := range e.status.Steps { + if ss.Name == stepName { + // sss is subStepStatus + for _, sss := range ss.SubStepsStatus { + if sss.Name == subStepName { + return sss + } + } + } + } + return common.WorkflowSubStepStatus{} +} +func (e *engine) getStepStatus(stepName string) common.WorkflowStepStatus { + // ss is step status + for _, ss := range e.status.Steps { + if ss.Name == stepName { + return ss + } + } + return common.WorkflowStepStatus{} +} diff --git a/pkg/workflow/workflow_test.go b/pkg/workflow/workflow_test.go index b22608d6dde18cd78816edf37bedb037e0768b44..55e3b34f547664cde3ce08a77f946b4a0f14f209 100644 --- a/pkg/workflow/workflow_test.go +++ b/pkg/workflow/workflow_test.go @@ -84,15 +84,21 @@ var _ = Describe("Test Workflow", func() { AppRevision: workflowStatus.AppRevision, Mode: common.WorkflowModeStep, Message: string(common.WorkflowStateExecuting), - Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, - }, { - Name: "s2", - Type: "failed", - Phase: common.WorkflowStepPhaseFailed, - }}, + Steps: []common.WorkflowStepStatus{ + { + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "failed", + Phase: common.WorkflowStepPhaseFailed, + }, + }, + }, })).Should(BeEquivalentTo("")) app, runners = makeTestCase([]oamcore.WorkflowStep{ @@ -129,17 +135,214 @@ var _ = Describe("Test Workflow", func() { Mode: common.WorkflowModeStep, Message: string(common.WorkflowStateSucceeded), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s3", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, + })).Should(BeEquivalentTo("")) + + By("Test failed with step group") + app, runners = makeTestCase([]oamcore.WorkflowStep{ + { + Name: "s1", + Type: "success", + }, + { + Name: "s2", + Type: "step-group", + SubSteps: []common.WorkflowSubStep{ + { + Name: "s2-sub1", + Type: "success", + }, + { + Name: "s2-sub2", + Type: "failed", + }, + }, + }, + { + Name: "s3", + Type: "success", + }, + }) + wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil) + ctx = monitorContext.NewTraceContext(context.Background(), "test-app") + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing)) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateExecuting)) + app.Status.Workflow.ContextBackend = nil + cleanStepTimeStamp(app.Status.Workflow) + Expect(cmp.Diff(*app.Status.Workflow, common.WorkflowStatus{ + AppRevision: app.Status.Workflow.AppRevision, + Mode: common.WorkflowModeStep, + Message: string(common.WorkflowStateExecuting), + Steps: []common.WorkflowStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "step-group", + Phase: common.WorkflowStepPhaseFailed, + }, + SubStepsStatus: []common.WorkflowSubStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s2-sub1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2-sub2", + Type: "failed", + Phase: common.WorkflowStepPhaseFailed, + }, + }}, + }}, + })).Should(BeEquivalentTo("")) + + By("Test success with step group") + app, runners = makeTestCase([]oamcore.WorkflowStep{ + { + Name: "s1", + Type: "success", + }, + { + Name: "s2", + Type: "step-group", + SubSteps: []common.WorkflowSubStep{ + { + Name: "s2-sub1", + Type: "success", + }, + { + Name: "s2-sub2", + Type: "success", + }, + }, + }, + { + Name: "s3", + Type: "success", + }, + }) + wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil) + ctx = monitorContext.NewTraceContext(context.Background(), "test-app") + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing)) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateSucceeded)) + app.Status.Workflow.ContextBackend = nil + cleanStepTimeStamp(app.Status.Workflow) + Expect(cmp.Diff(*app.Status.Workflow, common.WorkflowStatus{ + AppRevision: app.Status.Workflow.AppRevision, + Mode: common.WorkflowModeStep, + Message: string(common.WorkflowStateSucceeded), + Steps: []common.WorkflowStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "step-group", + Phase: common.WorkflowStepPhaseSucceeded, + }, + SubStepsStatus: []common.WorkflowSubStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s2-sub1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2-sub2", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, + }, { + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, + })).Should(BeEquivalentTo("")) + + By("Test success with step group and empty subSteps") + app, runners = makeTestCase([]oamcore.WorkflowStep{ + { + Name: "s1", + Type: "success", + }, + { + Name: "s2", + Type: "step-group", + SubSteps: []common.WorkflowSubStep{}, + }, + { + Name: "s3", + Type: "success", + }, + }) + wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil) + ctx = monitorContext.NewTraceContext(context.Background(), "test-app") + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing)) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateSucceeded)) + app.Status.Workflow.ContextBackend = nil + cleanStepTimeStamp(app.Status.Workflow) + Expect(cmp.Diff(*app.Status.Workflow, common.WorkflowStatus{ + AppRevision: app.Status.Workflow.AppRevision, + Mode: common.WorkflowModeStep, + Message: string(common.WorkflowStateSucceeded), + Steps: []common.WorkflowStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "step-group", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }}, })).Should(BeEquivalentTo("")) }) @@ -179,13 +382,17 @@ var _ = Describe("Test Workflow", func() { Message: MessageFailedAfterRetries, Suspend: true, Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "failed-after-retries", - Phase: common.WorkflowStepPhaseFailed, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "failed-after-retries", + Phase: common.WorkflowStepPhaseFailed, + }, }}, })).Should(BeEquivalentTo("")) @@ -223,17 +430,91 @@ var _ = Describe("Test Workflow", func() { Message: MessageFailedAfterRetries, Suspend: true, Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "failed-after-retries", - Phase: common.WorkflowStepPhaseFailed, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "failed-after-retries", + Phase: common.WorkflowStepPhaseFailed, + }, }, { - Name: "s3", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, + })).Should(BeEquivalentTo("")) + + By("Test failed-after-retries with step group in StepByStep mode") + app, runners = makeTestCase([]oamcore.WorkflowStep{ + { + Name: "s1", + Type: "success", + }, + { + Name: "s2", + Type: "step-group", + SubSteps: []common.WorkflowSubStep{ + { + Name: "s2-sub1", + Type: "success", + }, + { + Name: "s2-sub2", + Type: "failed-after-retries", + }, + }, + }, + { + Name: "s3", + Type: "success", + }, + }) + wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil) + ctx = monitorContext.NewTraceContext(context.Background(), "test-app") + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing)) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateSuspended)) + app.Status.Workflow.ContextBackend = nil + cleanStepTimeStamp(app.Status.Workflow) + Expect(cmp.Diff(*app.Status.Workflow, common.WorkflowStatus{ + AppRevision: app.Status.Workflow.AppRevision, + Mode: common.WorkflowModeStep, + Message: MessageFailedAfterRetries, + Suspend: true, + Steps: []common.WorkflowStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "step-group", + Phase: common.WorkflowStepPhaseFailed, + }, + SubStepsStatus: []common.WorkflowSubStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s2-sub1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2-sub2", + Type: "failed-after-retries", + Phase: common.WorkflowStepPhaseFailed, + }, + }}, }}, })).Should(BeEquivalentTo("")) }) @@ -326,13 +607,17 @@ var _ = Describe("Test Workflow", func() { Suspend: true, Message: string(common.WorkflowStateSuspended), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "suspend", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "suspend", + Phase: common.WorkflowStepPhaseSucceeded, + }, }}, })).Should(BeEquivalentTo("")) @@ -355,23 +640,97 @@ var _ = Describe("Test Workflow", func() { Mode: common.WorkflowModeStep, Message: string(common.WorkflowStateSucceeded), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "suspend", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "suspend", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s3", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }}, })).Should(BeEquivalentTo("")) state, err = wf.ExecuteSteps(ctx, revision, runners) Expect(err).ToNot(HaveOccurred()) Expect(state).Should(BeEquivalentTo(common.WorkflowStateSucceeded)) + + By("Test suspend with step group") + app, runners = makeTestCase([]oamcore.WorkflowStep{ + { + Name: "s1", + Type: "success", + }, + { + Name: "s2", + Type: "step-group", + SubSteps: []common.WorkflowSubStep{ + { + Name: "s2-sub1", + Type: "success", + }, + { + Name: "s2-sub2", + Type: "suspend", + }, + }, + }, + { + Name: "s3", + Type: "success", + }, + }) + wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil) + ctx = monitorContext.NewTraceContext(context.Background(), "test-app") + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing)) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateSuspended)) + app.Status.Workflow.ContextBackend = nil + cleanStepTimeStamp(app.Status.Workflow) + Expect(cmp.Diff(*app.Status.Workflow, common.WorkflowStatus{ + AppRevision: app.Status.Workflow.AppRevision, + Mode: common.WorkflowModeStep, + Suspend: true, + Message: string(common.WorkflowStateSuspended), + Steps: []common.WorkflowStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "step-group", + Phase: common.WorkflowStepPhaseSucceeded, + }, + SubStepsStatus: []common.WorkflowSubStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s2-sub1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2-sub2", + Type: "suspend", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, + }}, + })).Should(BeEquivalentTo("")) }) It("test for terminate", func() { @@ -401,13 +760,85 @@ var _ = Describe("Test Workflow", func() { Terminated: true, Message: string(common.WorkflowStateTerminated), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "terminate", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "terminate", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, + })).Should(BeEquivalentTo("")) + + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateTerminated)) + + By("Test terminate with step group") + app, runners = makeTestCase([]oamcore.WorkflowStep{ + { + Name: "s1", + Type: "success", + }, + { + Name: "s2", + Type: "step-group", + SubSteps: []common.WorkflowSubStep{ + { + Name: "s2-sub1", + Type: "success", + }, + { + Name: "s2-sub2", + Type: "terminate", + }, + }, + }, + }) + ctx = monitorContext.NewTraceContext(context.Background(), "test-app") + wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep, false, nil) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateInitializing)) + state, err = wf.ExecuteSteps(ctx, revision, runners) + Expect(err).ToNot(HaveOccurred()) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateTerminated)) + app.Status.Workflow.ContextBackend = nil + cleanStepTimeStamp(app.Status.Workflow) + Expect(cmp.Diff(*app.Status.Workflow, common.WorkflowStatus{ + AppRevision: app.Status.Workflow.AppRevision, + Mode: common.WorkflowModeStep, + Terminated: true, + Message: string(common.WorkflowStateTerminated), + Steps: []common.WorkflowStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2", + Type: "step-group", + Phase: common.WorkflowStepPhaseSucceeded, + }, + SubStepsStatus: []common.WorkflowSubStepStatus{{ + StepStatus: common.StepStatus{ + Name: "s2-sub1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }, { + StepStatus: common.StepStatus{ + Name: "s2-sub2", + Type: "terminate", + Phase: common.WorkflowStepPhaseSucceeded, + }, + }}, }}, })).Should(BeEquivalentTo("")) @@ -442,9 +873,11 @@ var _ = Describe("Test Workflow", func() { Mode: common.WorkflowModeStep, Message: string(common.WorkflowStateExecuting), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }}, })).Should(BeEquivalentTo("")) }) @@ -489,13 +922,17 @@ var _ = Describe("Test Workflow", func() { Mode: common.WorkflowModeDAG, Message: string(common.WorkflowStateExecuting), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s3", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }}, })).Should(BeEquivalentTo("")) @@ -514,17 +951,23 @@ var _ = Describe("Test Workflow", func() { Mode: common.WorkflowModeDAG, Message: string(common.WorkflowStateSucceeded), Steps: []common.WorkflowStepStatus{{ - Name: "s1", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s1", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s3", - Type: "success", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s3", + Type: "success", + Phase: common.WorkflowStepPhaseSucceeded, + }, }, { - Name: "s2", - Type: "pending", - Phase: common.WorkflowStepPhaseSucceeded, + StepStatus: common.StepStatus{ + Name: "s2", + Type: "pending", + Phase: common.WorkflowStepPhaseSucceeded, + }, }}, })).Should(BeEquivalentTo("")) }) @@ -573,19 +1016,27 @@ func makeTestCase(steps []oamcore.WorkflowStep) (*oamcore.Application, []wfTypes app.Name = "app" runners := []wfTypes.TaskRunner{} for _, step := range steps { - runners = append(runners, makeRunner(step.Name, step.Type)) + if step.SubSteps != nil { + subStepRunners := []wfTypes.TaskRunner{} + for _, subStep := range step.SubSteps { + subStepRunners = append(subStepRunners, makeRunner(subStep.Name, subStep.Type, nil)) + } + runners = append(runners, makeRunner(step.Name, step.Type, subStepRunners)) + } else { + runners = append(runners, makeRunner(step.Name, step.Type, nil)) + } } return app, runners } var pending bool -func makeRunner(name string, tpy string) wfTypes.TaskRunner { - var run func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) +func makeRunner(name string, tpy string, subTaskRunners []wfTypes.TaskRunner) wfTypes.TaskRunner { + var run func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) switch tpy { case "suspend": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: "suspend", Phase: common.WorkflowStepPhaseSucceeded, @@ -594,8 +1045,8 @@ func makeRunner(name string, tpy string) wfTypes.TaskRunner { }, nil } case "terminate": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: "terminate", Phase: common.WorkflowStepPhaseSucceeded, @@ -604,24 +1055,24 @@ func makeRunner(name string, tpy string) wfTypes.TaskRunner { }, nil } case "success": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: "success", Phase: common.WorkflowStepPhaseSucceeded, }, &wfTypes.Operation{}, nil } case "failed": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: "failed", Phase: common.WorkflowStepPhaseFailed, }, &wfTypes.Operation{}, nil } case "failed-after-retries": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: "failed-after-retries", Phase: common.WorkflowStepPhaseFailed, @@ -630,27 +1081,34 @@ func makeRunner(name string, tpy string) wfTypes.TaskRunner { }, nil } case "error": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: "error", Phase: common.WorkflowStepPhaseRunning, }, &wfTypes.Operation{}, errors.New("error for test") } case "wait-with-set-var": - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { v, _ := value.NewValue(`saved: true`, nil, "") err := ctx.SetVar(v) - return common.WorkflowStepStatus{ + return common.StepStatus{ Name: name, Type: "wait-with-set-var", Phase: common.WorkflowStepPhaseRunning, }, &wfTypes.Operation{}, err } - + case "step-group": + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ + Name: name, + Type: "step-group", + Phase: common.WorkflowStepPhaseRunning, + }, &wfTypes.Operation{}, nil + } default: - run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { - return common.WorkflowStepStatus{ + run = func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { + return common.StepStatus{ Name: name, Type: tpy, Phase: common.WorkflowStepPhaseSucceeded, @@ -670,6 +1128,7 @@ func makeRunner(name string, tpy string) wfTypes.TaskRunner { } return false }, + subTaskRunners: subTaskRunners, } } @@ -690,9 +1149,10 @@ metadata: ) type testTaskRunner struct { - name string - run func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) - checkPending func(ctx wfContext.Context) bool + name string + run func(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) + checkPending func(ctx wfContext.Context) bool + subTaskRunners []wfTypes.TaskRunner } // Name return step name. @@ -701,7 +1161,7 @@ func (tr *testTaskRunner) Name() string { } // Run execute task. -func (tr *testTaskRunner) Run(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.WorkflowStepStatus, *wfTypes.Operation, error) { +func (tr *testTaskRunner) Run(ctx wfContext.Context, options *wfTypes.TaskRunOptions) (common.StepStatus, *wfTypes.Operation, error) { return tr.run(ctx, nil) } @@ -710,10 +1170,20 @@ func (tr *testTaskRunner) Pending(ctx wfContext.Context) bool { return tr.checkPending(ctx) } +func (tr *testTaskRunner) SubTaskRunners() []wfTypes.TaskRunner { + return tr.subTaskRunners +} + func cleanStepTimeStamp(wfStatus *common.WorkflowStatus) { wfStatus.StartTime = metav1.Time{} - for index := range wfStatus.Steps { + for index, step := range wfStatus.Steps { wfStatus.Steps[index].FirstExecuteTime = metav1.Time{} wfStatus.Steps[index].LastExecuteTime = metav1.Time{} + if step.SubStepsStatus != nil { + for indexSubStep := range step.SubStepsStatus { + wfStatus.Steps[index].SubStepsStatus[indexSubStep].FirstExecuteTime = metav1.Time{} + wfStatus.Steps[index].SubStepsStatus[indexSubStep].LastExecuteTime = metav1.Time{} + } + } } } diff --git a/vela-templates/definitions/internal/workflowstep/step-group.cue b/vela-templates/definitions/internal/workflowstep/step-group.cue new file mode 100644 index 0000000000000000000000000000000000000000..558548af7a1b0df9c06ea766d3145fec55896f62 --- /dev/null +++ b/vela-templates/definitions/internal/workflowstep/step-group.cue @@ -0,0 +1,10 @@ +"step-group": { + type: "workflow-step" + annotations: {} + labels: {} + description: "step group" +} +template: { + // no parameters + parameter: {} +}