From 9fea82fd7b67df4dccd0334701b05a7e021d1944 Mon Sep 17 00:00:00 2001 From: "Jian.Li" <74582607+leejanee@users.noreply.github.com> Date: Tue, 19 Oct 2021 11:43:06 +0800 Subject: [PATCH] Feat: Record workflow execution state (#2479) * Feat: workflow status add timestamp * Feat: add workflow recorder * Fix: finish state process * Fix: test cases * Fix: revert comments history->recorder * Feat: omitempty execution time * Fix: workflow_test.go * Feat: add application test case for record * Feat: terminate record * Feat: trigger only by anno * Feat: remove initializers CRD --- apis/core.oam.dev/common/types.go | 12 +- .../common/zz_generated.deepcopy.go | 3 + .../core.oam.dev_applicationrevisions.yaml | 32 + .../crds/core.oam.dev_applications.yaml | 28 + .../crds/core.oam.dev_initializers.yaml | 1303 ----------------- .../core.oam.dev_applicationrevisions.yaml | 32 + .../crds/core.oam.dev_applications.yaml | 28 + .../core.oam.dev_applicationrevisions.yaml | 32 + .../crds/core.oam.dev_applications.yaml | 32 + .../crds/core.oam.dev_initializers.yaml | 16 + .../application/application_controller.go | 31 +- .../application_controller_test.go | 54 +- .../v1alpha2/application/workflow_test.go | 4 + pkg/oam/testutil/helper.go | 3 + pkg/workflow/interface.go | 2 + pkg/workflow/recorder/recorder.go | 156 ++ pkg/workflow/recorder/recorder_test.go | 113 ++ pkg/workflow/types/types.go | 2 + pkg/workflow/workflow.go | 51 +- pkg/workflow/workflow_test.go | 28 +- 20 files changed, 640 insertions(+), 1322 deletions(-) delete mode 100644 charts/vela-core/crds/core.oam.dev_initializers.yaml create mode 100644 pkg/workflow/recorder/recorder.go create mode 100644 pkg/workflow/recorder/recorder_test.go diff --git a/apis/core.oam.dev/common/types.go b/apis/core.oam.dev/common/types.go index e688c1fdb..f64a2669d 100644 --- a/apis/core.oam.dev/common/types.go +++ b/apis/core.oam.dev/common/types.go @@ -20,6 +20,7 @@ import ( "encoding/json" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" types "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime" @@ -198,7 +199,9 @@ const ( WorkflowStateTerminated WorkflowState = "terminated" // WorkflowStateSuspended means workflow is suspended manually, and it can be resumed. WorkflowStateSuspended WorkflowState = "suspended" - // WorkflowStateFinished means workflow is running successfully, all steps finished. + // WorkflowStateSucceeded means workflow is running successfully, all steps finished. + WorkflowStateSucceeded WorkflowState = "Succeeded" + // WorkflowStateFinished means workflow is end. WorkflowStateFinished WorkflowState = "finished" // WorkflowStateExecuting means workflow is still running or waiting some steps. WorkflowStateExecuting WorkflowState = "executing" @@ -250,6 +253,10 @@ type WorkflowStepStatus struct { // A brief CamelCase message indicating details about why the workflowStep is in this state. Reason string `json:"reason,omitempty"` SubSteps *SubStepsStatus `json:"subSteps,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 @@ -305,9 +312,12 @@ type WorkflowStatus struct { Suspend bool `json:"suspend"` Terminated bool `json:"terminated"` + Finished bool `json:"finished"` ContextBackend *corev1.ObjectReference `json:"contextBackend,omitempty"` Steps []WorkflowStepStatus `json:"steps,omitempty"` + + StartTime metav1.Time `json:"startTime,omitempty"` } // SubStepsStatus record the status of workflow steps. diff --git a/apis/core.oam.dev/common/zz_generated.deepcopy.go b/apis/core.oam.dev/common/zz_generated.deepcopy.go index ccf9fa113..766c22b1e 100644 --- a/apis/core.oam.dev/common/zz_generated.deepcopy.go +++ b/apis/core.oam.dev/common/zz_generated.deepcopy.go @@ -595,6 +595,7 @@ func (in *WorkflowStatus) DeepCopyInto(out *WorkflowStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + in.StartTime.DeepCopyInto(&out.StartTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowStatus. @@ -615,6 +616,8 @@ func (in *WorkflowStepStatus) DeepCopyInto(out *WorkflowStepStatus) { *out = new(SubStepsStatus) (*in).DeepCopyInto(*out) } + in.FirstExecuteTime.DeepCopyInto(&out.FirstExecuteTime) + in.LastExecuteTime.DeepCopyInto(&out.LastExecuteTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowStepStatus. diff --git a/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml b/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml index 306b2ace4..075b50992 100644 --- a/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml +++ b/charts/vela-core/crds/core.oam.dev_applicationrevisions.yaml @@ -966,16 +966,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -1043,6 +1058,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated @@ -3094,16 +3110,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -3171,6 +3202,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated diff --git a/charts/vela-core/crds/core.oam.dev_applications.yaml b/charts/vela-core/crds/core.oam.dev_applications.yaml index eadd800a4..18d952e62 100644 --- a/charts/vela-core/crds/core.oam.dev_applications.yaml +++ b/charts/vela-core/crds/core.oam.dev_applications.yaml @@ -648,15 +648,28 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -711,6 +724,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated @@ -1441,15 +1455,28 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -1504,6 +1531,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated diff --git a/charts/vela-core/crds/core.oam.dev_initializers.yaml b/charts/vela-core/crds/core.oam.dev_initializers.yaml deleted file mode 100644 index eacda8f47..000000000 --- a/charts/vela-core/crds/core.oam.dev_initializers.yaml +++ /dev/null @@ -1,1303 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - name: initializers.core.oam.dev -spec: - group: core.oam.dev - names: - categories: - - oam - kind: Initializer - listKind: InitializerList - plural: initializers - shortNames: - - init - singular: initializer - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.status - name: PHASE - type: string - - jsonPath: .metadata.creationTimestamp - name: AGE - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Initializer is the Schema for the Initializer API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: A InitializerSpec defines the desired state of a Initializer. - properties: - appTemplate: - description: AppTemplate indicates the application template to render - and deploy an system application. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this - representation of an object. Servers should convert recognized - schemas to the latest internal value, and may reject unrecognized - values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource - this object represents. Servers may infer this from the endpoint - the client submits requests to. Cannot be updated. In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - description: ApplicationSpec is the spec of Application - properties: - components: - items: - description: ApplicationComponent describe the component - of application - properties: - dependsOn: - items: - type: string - type: array - externalRevision: - description: ExternalRevision specified the component - revisionName - type: string - inputs: - description: StepInputs defines variable input of WorkflowStep - items: - properties: - from: - type: string - parameterKey: - type: string - required: - - from - - parameterKey - type: object - type: array - name: - 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 - scopes: - additionalProperties: - type: string - description: scopes in ApplicationComponent defines - the component-level scopes the format is - pairs, the key represents type of `ScopeDefinition` - while the value represent the name of scope instance. - type: object - x-kubernetes-preserve-unknown-fields: true - traits: - description: Traits define the trait of one component, - the type must be array to keep the order. - items: - description: ApplicationTrait defines the trait of - application - properties: - properties: - type: object - x-kubernetes-preserve-unknown-fields: true - type: - type: string - required: - - type - type: object - type: array - type: - type: string - required: - - name - - type - type: object - type: array - policies: - description: Policies defines the global policies for all - components in the app, e.g. security, metrics, gitops, multi-cluster - placement rules, etc. Policies are applied after components - are rendered and before workflow steps are executed. - items: - description: AppPolicy defines a global policy for all components - in the app. - properties: - name: - description: Name is the unique name of the policy. - type: string - properties: - type: object - x-kubernetes-preserve-unknown-fields: true - type: - type: string - required: - - name - - type - type: object - type: array - rolloutPlan: - description: RolloutPlan is the details on how to rollout - the resources The controller simply replace the old resources - with the new one if there is no rollout plan involved - properties: - batchPartition: - description: All pods in the batches up to the batchPartition - (included) will have the target resource specification - while the rest still have the source resource This is - designed for the operators to manually rollout Default - is the the number of batches which will rollout all - the batches - format: int32 - type: integer - canaryMetric: - description: CanaryMetric provides a way for the rollout - process to automatically check certain metrics before - complete the process - items: - description: CanaryMetric holds the reference to metrics - used for canary analysis - properties: - interval: - description: Interval represents the windows size - type: string - metricsRange: - description: Range value accepted for this metric - properties: - max: - anyOf: - - type: integer - - type: string - description: Maximum value - x-kubernetes-int-or-string: true - min: - anyOf: - - type: integer - - type: string - description: Minimum value - x-kubernetes-int-or-string: true - type: object - name: - description: Name of the metric - type: string - templateRef: - description: TemplateRef references a metric template - object - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an - object instead of an entire object, this string - should contain a valid JSON/Go field access - statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to - a container within a pod, this would take - on a value like: "spec.containers{name}" (where - "name" refers to the name of the container - that triggered the event) or if no container - name is specified "spec.containers[2]" (container - with index 2 in this pod). This syntax is - chosen only to have some well-defined way - of referencing a part of an object. TODO: - this design is not final and this field is - subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which - this reference is made, if any. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - name - type: object - type: array - numBatches: - description: The number of batches, default = 1 - format: int32 - type: integer - paused: - description: Paused the rollout, default is false - type: boolean - rolloutBatches: - description: The exact distribution among batches. its - size has to be exactly the same as the NumBatches (if - set) The total number cannot exceed the targetSize or - the size of the source resource We will IGNORE the last - batch's replica field if it's a percentage since round - errors can lead to inaccurate sum We highly recommend - to leave the last batch's replica field empty - items: - description: RolloutBatch is used to describe how the - each batch rollout should be - properties: - batchRolloutWebhooks: - description: RolloutWebhooks provides a way for - the batch rollout to interact with an external - process - items: - description: RolloutWebhook holds the reference - to external checks used for canary analysis - properties: - expectedStatus: - description: ExpectedStatus contains all the - expected http status code that we will accept - as success - items: - type: integer - type: array - metadata: - additionalProperties: - type: string - description: Metadata (key-value pairs) for - this webhook - type: object - method: - description: Method the HTTP call method, - default is POST - type: string - name: - description: Name of this webhook - type: string - type: - description: Type of this webhook - type: string - url: - description: URL address of this webhook - type: string - required: - - name - - type - - url - type: object - type: array - canaryMetric: - description: CanaryMetric provides a way for the - batch rollout process to automatically check certain - metrics before moving to the next batch - items: - description: CanaryMetric holds the reference - to metrics used for canary analysis - properties: - interval: - description: Interval represents the windows - size - type: string - metricsRange: - description: Range value accepted for this - metric - properties: - max: - anyOf: - - type: integer - - type: string - description: Maximum value - x-kubernetes-int-or-string: true - min: - anyOf: - - type: integer - - type: string - description: Minimum value - x-kubernetes-int-or-string: true - type: object - name: - description: Name of the metric - type: string - templateRef: - description: TemplateRef references a metric - template object - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece - of an object instead of an entire object, - this string should contain a valid JSON/Go - field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference - is to a container within a pod, this - would take on a value like: "spec.containers{name}" - (where "name" refers to the name of - the container that triggered the event) - or if no container name is specified - "spec.containers[2]" (container with - index 2 in this pod). This syntax is - chosen only to have some well-defined - way of referencing a part of an object. - TODO: this design is not final and this - field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More - info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion - to which this reference is made, if - any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - name - type: object - type: array - instanceInterval: - description: The wait time, in seconds, between - instances upgrades, default = 0 - format: int32 - type: integer - maxUnavailable: - anyOf: - - type: integer - - type: string - description: MaxUnavailable is the max allowed number - of pods that is unavailable during the upgrade. - We will mark the batch as ready as long as there - are less or equal number of pods unavailable than - this number. default = 0 - x-kubernetes-int-or-string: true - podList: - description: The list of Pods to get upgraded it - is mutually exclusive with the Replicas field - items: - type: string - type: array - replicas: - anyOf: - - type: integer - - type: string - description: 'Replicas is the number of pods to - upgrade in this batch it can be an absolute number - (ex: 5) or a percentage of total pods we will - ignore the percentage of the last batch to just - fill the gap it is mutually exclusive with the - PodList field' - x-kubernetes-int-or-string: true - type: object - type: array - rolloutStrategy: - description: RolloutStrategy defines strategies for the - rollout plan The default is IncreaseFirstRolloutStrategyType - type: string - rolloutWebhooks: - description: RolloutWebhooks provide a way for the rollout - to interact with an external process - items: - description: RolloutWebhook holds the reference to external - checks used for canary analysis - properties: - expectedStatus: - description: ExpectedStatus contains all the expected - http status code that we will accept as success - items: - type: integer - type: array - metadata: - additionalProperties: - type: string - description: Metadata (key-value pairs) for this - webhook - type: object - method: - description: Method the HTTP call method, default - is POST - type: string - name: - description: Name of this webhook - type: string - type: - description: Type of this webhook - type: string - url: - description: URL address of this webhook - type: string - required: - - name - - type - - url - type: object - type: array - targetSize: - description: The size of the target resource. The default - is the same as the size of the source resource. - format: int32 - type: integer - type: object - workflow: - description: 'Workflow defines how to customize the control - logic. If workflow is specified, Vela won''t apply any resource, - but provide rendered output in AppRevision. Workflow steps - are executed in array order, and each step: - will have - a context in annotation. - should mark "finish" phase in - status.conditions.' - properties: - steps: - items: - description: WorkflowStep defines how to execute a workflow - step. - 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: object - required: - - components - type: object - status: - description: AppStatus defines the observed state of Application - properties: - appliedResources: - description: AppliedResources record the resources that the workflow - step apply. - items: - description: ClusterObjectReference defines the object reference - with cluster. - properties: - apiVersion: - description: API version of the referent. - type: string - cluster: - type: string - creator: - description: ResourceCreatorRole defines the resource - creator. - type: string - fieldPath: - description: 'If referring to a piece of an object instead - of an entire object, this string should contain a - valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container - within a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container - that triggered the event) or if no container name - is specified "spec.containers[2]" (container with - index 2 in this pod). This syntax is chosen only to - have some well-defined way of referencing a part of - an object. TODO: this design is not final and this - field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this - reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - type: array - components: - description: Components record the related Components created - by Application Controller - items: - description: 'ObjectReference contains enough information - to let you inspect or modify the referred object. --- - New uses of this type are discouraged because of difficulty - describing its usage when embedded in APIs. 1. Ignored - fields. It includes many fields which are not generally - honored. For instance, ResourceVersion and FieldPath - are both very rarely valid in actual usage. 2. Invalid - usage help. It is impossible to add specific help for - individual usage. In most embedded usages, there are - particular restrictions like, "must refer only to - types A and B" or "UID not honored" or "name must be restricted". Those - cannot be well described when embedded. 3. Inconsistent - validation. Because the usages are different, the validation - rules are different by usage, which makes it hard for - users to predict what will happen. 4. The fields are - both imprecise and overly precise. Kind is not a precise - mapping to a URL. This can produce ambiguity during - interpretation and require a REST mapping. In most cases, - the dependency is on the group,resource tuple and - the version of the actual struct is irrelevant. 5. We - cannot easily change it. Because this type is embedded - in many locations, updates to this type will affect - numerous schemas. Don''t make new APIs embed an underspecified - API type they do not control. Instead of using this type, - create a locally provided and used type that is well-focused - on your reference. For example, ServiceReferences for - admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 - .' - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead - of an entire object, this string should contain a - valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container - within a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container - that triggered the event) or if no container name - is specified "spec.containers[2]" (container with - index 2 in this pod). This syntax is chosen only to - have some well-defined way of referencing a part of - an object. TODO: this design is not final and this - field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this - reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - type: array - conditions: - description: Conditions of the resource. - items: - description: A Condition that may apply to a resource. - properties: - lastTransitionTime: - description: LastTransitionTime is the last time this - condition transitioned from one status to another. - format: date-time - type: string - message: - description: A Message containing details about this - condition's last transition from one status to another, - if any. - type: string - reason: - description: A Reason for this condition's last transition - from one status to another. - type: string - status: - description: Status of this condition; is it currently - True, False, or Unknown? - type: string - type: - description: Type of this condition. At most one of - each condition type may apply to a resource at any - point in time. - type: string - required: - - lastTransitionTime - - reason - - status - - type - type: object - type: array - latestRevision: - description: LatestRevision of the application configuration - it generates - properties: - name: - type: string - revision: - format: int64 - type: integer - revisionHash: - description: RevisionHash record the hash value of the - spec of ApplicationRevision object. - type: string - required: - - name - - revision - type: object - observedGeneration: - description: The generation observed by the application controller. - format: int64 - type: integer - resourceTracker: - description: ResourceTracker record the status of the ResourceTracker - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead - of an entire object, this string should contain a valid - JSON/Go field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container - within a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that - triggered the event) or if no container name is specified - "spec.containers[2]" (container with index 2 in this - pod). This syntax is chosen only to have some well-defined - way of referencing a part of an object. TODO: this design - is not final and this field is subject to change in - the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference - is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - rollout: - description: AppRolloutStatus defines the observed state of - AppRollout - properties: - LastSourceAppRevision: - description: LastSourceAppRevision contains the name of - the app that we need to upgrade from. We will restart - the rollout if this is not the same as the spec - type: string - batchRollingState: - description: BatchRollingState only meaningful when the - Status is rolling - type: string - conditions: - description: Conditions of the resource. - items: - description: A Condition that may apply to a resource. - properties: - lastTransitionTime: - description: LastTransitionTime is the last time - this condition transitioned from one status to - another. - format: date-time - type: string - message: - description: A Message containing details about - this condition's last transition from one status - to another, if any. - type: string - reason: - description: A Reason for this condition's last - transition from one status to another. - type: string - status: - description: Status of this condition; is it currently - True, False, or Unknown? - type: string - type: - description: Type of this condition. At most one - of each condition type may apply to a resource - at any point in time. - type: string - required: - - lastTransitionTime - - reason - - status - - type - type: object - type: array - currentBatch: - description: The current batch the rollout is working - on/blocked it starts from 0 - format: int32 - type: integer - lastAppliedPodTemplateIdentifier: - description: lastAppliedPodTemplateIdentifier is a string - that uniquely represent the last pod template each workload - type could use different ways to identify that so we - cannot compare between resources We update this field - only after a successful rollout - type: string - lastTargetAppRevision: - description: LastUpgradedTargetAppRevision contains the - name of the app that we upgraded to We will restart - the rollout if this is not the same as the spec - type: string - rollingState: - description: RollingState is the Rollout State - type: string - rolloutOriginalSize: - description: RolloutTargetSize is the size of the target - resources. This is determined once the initial spec - verification and does not change until the rollout is - restarted - format: int32 - type: integer - rolloutTargetSize: - description: RolloutTargetSize is the size of the target - resources. This is determined once the initial spec - verification and does not change until the rollout is - restarted - format: int32 - type: integer - targetGeneration: - description: NewPodTemplateIdentifier is a string that - uniquely represent the new pod template each workload - type could use different ways to identify that so we - cannot compare between resources - type: string - upgradedReadyReplicas: - description: UpgradedReadyReplicas is the number of Pods - upgraded by the rollout controller that have a Ready - Condition. - format: int32 - type: integer - upgradedReplicas: - description: UpgradedReplicas is the number of Pods upgraded - by the rollout controller - format: int32 - type: integer - required: - - currentBatch - - lastTargetAppRevision - - rollingState - - upgradedReadyReplicas - - upgradedReplicas - type: object - services: - description: Services record the status of the application - services - items: - description: ApplicationComponentStatus record the health - status of App component - properties: - env: - type: string - healthy: - type: boolean - message: - type: string - name: - type: string - scopes: - items: - description: 'ObjectReference contains enough information - to let you inspect or modify the referred object. - --- New uses of this type are discouraged because - of difficulty describing its usage when embedded - in APIs. 1. Ignored fields. It includes many fields - which are not generally honored. For instance, - ResourceVersion and FieldPath are both very rarely - valid in actual usage. 2. Invalid usage help. It - is impossible to add specific help for individual - usage. In most embedded usages, there are particular restrictions - like, "must refer only to types A and B" or "UID - not honored" or "name must be restricted". Those - cannot be well described when embedded. 3. Inconsistent - validation. Because the usages are different, the - validation rules are different by usage, which makes - it hard for users to predict what will happen. 4. - The fields are both imprecise and overly precise. Kind - is not a precise mapping to a URL. This can produce - ambiguity during interpretation and require - a REST mapping. In most cases, the dependency is - on the group,resource tuple and the version - of the actual struct is irrelevant. 5. We cannot - easily change it. Because this type is embedded - in many locations, updates to this type will - affect numerous schemas. Don''t make new APIs embed - an underspecified API type they do not control. - Instead of using this type, create a locally provided - and used type that is well-focused on your reference. - For example, ServiceReferences for admission registration: - https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 - .' - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object - instead of an entire object, this string should - contain a valid JSON/Go field access statement, - such as desiredState.manifest.containers[2]. - For example, if the object reference is to a - container within a pod, this would take on a - value like: "spec.containers{name}" (where "name" - refers to the name of the container that triggered - the event) or if no container name is specified - "spec.containers[2]" (container with index 2 - in this pod). This syntax is chosen only to - have some well-defined way of referencing a - part of an object. TODO: this design is not - final and this field is subject to change in - the future.' - type: string - kind: - description: 'Kind of the referent. More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which - this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - type: array - traits: - items: - description: ApplicationTraitStatus records the trait - health status - properties: - healthy: - type: boolean - message: - type: string - type: - type: string - required: - - healthy - - type - type: object - type: array - workloadDefinition: - description: WorkloadDefinition is the definition of - a WorkloadDefinition, such as deployments/apps.v1 - properties: - apiVersion: - type: string - kind: - type: string - required: - - apiVersion - - kind - type: object - required: - - healthy - - name - type: object - type: array - status: - description: ApplicationPhase is a label for the condition - of a application at the current time - type: string - workflow: - description: Workflow record the status of workflow - properties: - appRevision: - type: string - contextBackend: - description: 'ObjectReference contains enough information - to let you inspect or modify the referred object. --- - New uses of this type are discouraged because of difficulty - describing its usage when embedded in APIs. 1. Ignored - fields. It includes many fields which are not generally - honored. For instance, ResourceVersion and FieldPath - are both very rarely valid in actual usage. 2. Invalid - usage help. It is impossible to add specific help for - individual usage. In most embedded usages, there are - particular restrictions like, "must refer only to - types A and B" or "UID not honored" or "name must be - restricted". Those cannot be well described when - embedded. 3. Inconsistent validation. Because the - usages are different, the validation rules are different - by usage, which makes it hard for users to predict what - will happen. 4. The fields are both imprecise and overly - precise. Kind is not a precise mapping to a URL. This - can produce ambiguity during interpretation and - require a REST mapping. In most cases, the dependency - is on the group,resource tuple and the version of - the actual struct is irrelevant. 5. We cannot easily - change it. Because this type is embedded in many locations, - updates to this type will affect numerous schemas. Don''t - make new APIs embed an underspecified API type they - do not control. Instead of using this type, create a - locally provided and used type that is well-focused - on your reference. For example, ServiceReferences for - admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 - .' - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object - instead of an entire object, this string should - contain a valid JSON/Go field access statement, - such as desiredState.manifest.containers[2]. For - example, if the object reference is to a container - within a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container - that triggered the event) or if no container name - is specified "spec.containers[2]" (container with - index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part - of an object. TODO: this design is not final and - this field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this - reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - mode: - description: WorkflowMode describes the mode of workflow - type: string - steps: - items: - description: WorkflowStepStatus 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 - 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 - type: - type: string - required: - - id - type: object - type: array - suspend: - type: boolean - terminated: - type: boolean - required: - - mode - - suspend - - terminated - type: object - type: object - type: object - dependsOn: - description: DependsOn indicates the other initializers that this - depends on. It will not apply its components until all dependencies - exist. - items: - description: DependsOn refer to an object which Initializer depends - on - properties: - ref: - description: 'ObjectReference contains enough information to - let you inspect or modify the referred object. --- New uses - of this type are discouraged because of difficulty describing - its usage when embedded in APIs. 1. Ignored fields. It includes - many fields which are not generally honored. For instance, - ResourceVersion and FieldPath are both very rarely valid in - actual usage. 2. Invalid usage help. It is impossible to - add specific help for individual usage. In most embedded - usages, there are particular restrictions like, "must - refer only to types A and B" or "UID not honored" or "name - must be restricted". Those cannot be well described when - embedded. 3. Inconsistent validation. Because the usages - are different, the validation rules are different by usage, - which makes it hard for users to predict what will happen. 4. - The fields are both imprecise and overly precise. Kind is - not a precise mapping to a URL. This can produce ambiguity during - interpretation and require a REST mapping. In most cases, - the dependency is on the group,resource tuple and the - version of the actual struct is irrelevant. 5. We cannot - easily change it. Because this type is embedded in many locations, - updates to this type will affect numerous schemas. Don''t - make new APIs embed an underspecified API type they do not - control. Instead of using this type, create a locally provided - and used type that is well-focused on your reference. For - example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 - .' - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead - of an entire object, this string should contain a valid - JSON/Go field access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container - within a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that - triggered the event) or if no container name is specified - "spec.containers[2]" (container with index 2 in this pod). - This syntax is chosen only to have some well-defined way - of referencing a part of an object. TODO: this design - is not final and this field is subject to change in the - future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference - is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - ref - type: object - type: array - required: - - appTemplate - type: object - status: - description: InitializerStatus is the status of Initializer - properties: - conditions: - description: Conditions of the resource. - items: - description: A Condition that may apply to a resource. - properties: - lastTransitionTime: - description: LastTransitionTime is the last time this condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A Message containing details about this condition's - last transition from one status to another, if any. - type: string - reason: - description: A Reason for this condition's last transition from - one status to another. - type: string - status: - description: Status of this condition; is it currently True, - False, or Unknown? - type: string - type: - description: Type of this condition. At most one of each condition - type may apply to a resource at any point in time. - type: string - required: - - lastTransitionTime - - reason - - status - - type - type: object - type: array - observedGeneration: - description: The generation observed by the Initializer controller. - format: int64 - type: integer - status: - description: InitializerPhase is a label for the condition of a initializer - at the current time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml b/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml index 306b2ace4..075b50992 100644 --- a/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_applicationrevisions.yaml @@ -966,16 +966,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -1043,6 +1058,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated @@ -3094,16 +3110,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -3171,6 +3202,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated diff --git a/charts/vela-minimal/crds/core.oam.dev_applications.yaml b/charts/vela-minimal/crds/core.oam.dev_applications.yaml index eadd800a4..18d952e62 100644 --- a/charts/vela-minimal/crds/core.oam.dev_applications.yaml +++ b/charts/vela-minimal/crds/core.oam.dev_applications.yaml @@ -648,15 +648,28 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -711,6 +724,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated @@ -1441,15 +1455,28 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -1504,6 +1531,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated 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 5813fd711..e85c3558d 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 @@ -966,16 +966,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -1043,6 +1058,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated @@ -3094,16 +3110,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -3171,6 +3202,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated 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 f67058282..64038d672 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 @@ -882,16 +882,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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. @@ -956,6 +971,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated @@ -1940,16 +1956,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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. @@ -2014,6 +2045,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated diff --git a/legacy/charts/vela-core-legacy/crds/core.oam.dev_initializers.yaml b/legacy/charts/vela-core-legacy/crds/core.oam.dev_initializers.yaml index 9dc3e6463..6fd5cb1b6 100644 --- a/legacy/charts/vela-core-legacy/crds/core.oam.dev_initializers.yaml +++ b/legacy/charts/vela-core-legacy/crds/core.oam.dev_initializers.yaml @@ -1085,16 +1085,31 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + finished: + type: boolean mode: description: WorkflowMode describes the mode of workflow type: string + startTime: + format: date-time + type: string steps: items: description: WorkflowStepStatus record the status of a workflow step 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 @@ -1162,6 +1177,7 @@ spec: terminated: type: boolean required: + - finished - mode - suspend - terminated diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index d9c1b1c83..20ff9632c 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -175,7 +175,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return r.endWithNegativeCondition(ctx, app, condition.ErrorCondition("Workflow", err), common.ApplicationRunningWorkflow) } - workflowState, err := workflow.NewWorkflow(app, r.Client, appFile.WorkflowMode).ExecuteSteps(ctx, handler.currentAppRev, steps) + wf := workflow.NewWorkflow(app, r.Client, appFile.WorkflowMode) + workflowState, err := wf.ExecuteSteps(ctx, handler.currentAppRev, steps) if err != nil { klog.Error(err, "[handle workflow]") r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedWorkflow, err)) @@ -185,14 +186,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu handler.addServiceStatus(false, app.Status.Services...) handler.addAppliedResource(app.Status.AppliedResources...) app.Status.AppliedResources = handler.appliedResources + app.Status.Services = handler.services switch workflowState { case common.WorkflowStateSuspended: return ctrl.Result{}, r.patchStatus(ctx, app, common.ApplicationWorkflowSuspending) case common.WorkflowStateTerminated: + if err := r.doWorkflowFinish(app, wf); err != nil { + return r.endWithNegativeCondition(ctx, app, condition.ErrorCondition("DoWorkflowFinish", err), common.ApplicationRunningWorkflow) + } return ctrl.Result{}, r.patchStatus(ctx, app, common.ApplicationWorkflowTerminated) case common.WorkflowStateExecuting: return reconcile.Result{RequeueAfter: baseWorkflowBackoffWaitTime}, r.patchStatus(ctx, app, common.ApplicationRunningWorkflow) - case common.WorkflowStateFinished: + case common.WorkflowStateSucceeded: wfStatus := app.Status.Workflow if wfStatus != nil { ref, err := handler.DispatchAndGC(ctx) @@ -210,10 +215,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } app.Status.ResourceTracker = ref } + if err := r.doWorkflowFinish(app, wf); err != nil { + return r.endWithNegativeCondition(ctx, app, condition.ErrorCondition("DoWorkflowFinish", err), common.ApplicationRunningWorkflow) + } + app.Status.SetConditions(condition.ReadyCondition("WorkflowFinished")) + r.Recorder.Event(app, event.Normal(velatypes.ReasonApplied, velatypes.MessageWorkflowFinished)) + klog.Info("Application manifests has applied by workflow successfully", "application", klog.KObj(app)) + return ctrl.Result{}, r.patchStatus(ctx, app, common.ApplicationWorkflowFinished) + case common.WorkflowStateFinished: + if status := app.Status.Workflow; status != nil && status.Terminated { + return ctrl.Result{}, nil + } } - app.Status.SetConditions(condition.ReadyCondition("WorkflowFinished")) - r.Recorder.Event(app, event.Normal(velatypes.ReasonApplied, velatypes.MessageWorkflowFinished)) - klog.Info("Application manifests has applied by workflow successfully", "application", klog.KObj(app)) } else { var comps []*velatypes.ComponentManifest comps, err = appFile.GenerateComponentManifests() @@ -342,6 +355,14 @@ func (r *Reconciler) patchStatus(ctx context.Context, app *v1beta1.Application, return r.Client.Status().Patch(ctx, app, client.Merge) } +func (r *Reconciler) doWorkflowFinish(app *v1beta1.Application, wf workflow.Workflow) error { + if err := wf.Trace(); err != nil { + return errors.WithMessage(err, "record workflow state") + } + app.Status.Workflow.Finished = true + return nil +} + // appWillRollout judge whether the application will be released by rollout. // If it's true, application controller will only create or update application revision but not emit any other K8s // resources into the cluster. Rollout controller will do real release works. diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go index 80b9df4f4..1fe43d057 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller_test.go @@ -26,8 +26,6 @@ import ( "strconv" "time" - "github.com/oam-dev/kubevela/pkg/oam/testutil" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -52,8 +50,10 @@ import ( velatypes "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/controller/utils" "github.com/oam-dev/kubevela/pkg/oam" + "github.com/oam-dev/kubevela/pkg/oam/testutil" "github.com/oam-dev/kubevela/pkg/oam/util" common2 "github.com/oam-dev/kubevela/pkg/utils/common" + wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types" ) // TODO: Refactor the tests to not copy and paste duplicated code 10 times @@ -1465,6 +1465,7 @@ var _ = Describe("Test Application Controller", func() { checkApp.Spec.Components[0].Properties = &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","2000"],"image":"nginx"}`)} Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil()) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &v1beta1.Application{} Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) @@ -1480,6 +1481,7 @@ var _ = Describe("Test Application Controller", func() { checkApp.Spec.Components[0].Traits[0].Properties = &runtime.RawExtension{Raw: []byte(`{"targetRevision":"myweb1-v3"}`)} Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil()) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &v1beta1.Application{} Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) Expect(checkApp.Status.LatestRevision.Name).Should(BeEquivalentTo(appRevName)) @@ -1533,6 +1535,7 @@ var _ = Describe("Test Application Controller", func() { testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) // second reconcile apply all resources testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp := &v1beta1.Application{} Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) @@ -1579,6 +1582,7 @@ var _ = Describe("Test Application Controller", func() { testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) // second reconcile apply all resources testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp := &v1beta1.Application{} Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) @@ -1628,6 +1632,7 @@ var _ = Describe("Test Application Controller", func() { testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) // second reconcile apply all resources testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp := &v1beta1.Application{} Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) @@ -1798,6 +1803,7 @@ var _ = Describe("Test Application Controller", func() { Expect(expDeployment.Spec.Template.Spec.Containers[0].Command).Should(BeEquivalentTo([]string{"sleep", "100"})) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &v1beta1.Application{} Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) @@ -1880,6 +1886,7 @@ var _ = Describe("Test Application Controller", func() { expDeployment.Status.ReadyReplicas = 1 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &v1beta1.Application{} @@ -1968,6 +1975,7 @@ var _ = Describe("Test Application Controller", func() { expDeployment.Status.ReadyReplicas = 1 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &v1beta1.Application{} @@ -2069,6 +2077,48 @@ var _ = Describe("Test Application Controller", func() { })) }) + It("app record execution state with controllerRevision", func() { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vela-test-app-trace", + }, + } + + app := appwithNoTrait.DeepCopy() + app.Name = "vela-test-app-trace" + app.SetNamespace(ns.Name) + app.Annotations = map[string]string{wfTypes.AnnotationPublishVersion: "v134"} + Expect(k8sClient.Create(ctx, ns)).Should(BeNil()) + Expect(k8sClient.Create(ctx, app)).Should(BeNil()) + + appKey := client.ObjectKey{ + Name: app.Name, + Namespace: app.Namespace, + } + + testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) + checkApp := &v1beta1.Application{} + Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) + Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) + recorder := &v1.ControllerRevision{} + Expect(k8sClient.Get(ctx, client.ObjectKey{ + Name: fmt.Sprintf("record-%s-v134", app.Name), + Namespace: app.Namespace, + }, recorder)).Should(BeNil()) + + checkApp.Annotations[wfTypes.AnnotationPublishVersion] = "v135" + Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil()) + testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey}) + checkApp = &v1beta1.Application{} + Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil()) + Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning)) + Expect(k8sClient.Get(ctx, client.ObjectKey{ + Name: fmt.Sprintf("record-%s-v135", app.Name), + Namespace: app.Namespace, + }, recorder)).Should(BeNil()) + + }) + }) const ( diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/workflow_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/workflow_test.go index a8ecb1fad..2cdb41c93 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/workflow_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/workflow_test.go @@ -218,6 +218,7 @@ var _ = Describe("Test Workflow", func() { triggerWorkflowStepToSucceed(stepObj) Expect(k8sClient.Update(ctx, stepObj)).Should(BeNil()) + tryReconcile(reconciler, appWithWorkflow.Name, appWithWorkflow.Namespace) tryReconcile(reconciler, appWithWorkflow.Name, appWithWorkflow.Namespace) tryReconcile(reconciler, appWithWorkflow.Name, appWithWorkflow.Namespace) @@ -416,6 +417,7 @@ var _ = Describe("Test Workflow", func() { expDeployment.Status.ReadyReplicas = 1 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &oamcore.Application{} @@ -513,6 +515,7 @@ var _ = Describe("Test Workflow", func() { expDeployment.Status.ReadyReplicas = 1 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &oamcore.Application{} @@ -614,6 +617,7 @@ var _ = Describe("Test Workflow", func() { expDeployment.Status.ReadyReplicas = 1 Expect(k8sClient.Status().Update(ctx, expDeployment)).Should(BeNil()) + testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey}) checkApp = &oamcore.Application{} diff --git a/pkg/oam/testutil/helper.go b/pkg/oam/testutil/helper.go index cf87be217..6204824a6 100644 --- a/pkg/oam/testutil/helper.go +++ b/pkg/oam/testutil/helper.go @@ -61,6 +61,9 @@ func ReconcileOnceAfterFinalizer(r reconcile.Reconciler, req reconcile.Request) if result, err := r.Reconcile(context.TODO(), req); err != nil { return result, err } + if result, err := r.Reconcile(context.TODO(), req); err != nil { + return result, err + } return r.Reconcile(context.TODO(), req) } diff --git a/pkg/workflow/interface.go b/pkg/workflow/interface.go index 234351b22..3de865021 100644 --- a/pkg/workflow/interface.go +++ b/pkg/workflow/interface.go @@ -28,4 +28,6 @@ type Workflow interface { // ExecuteSteps executes the steps of an Application with given steps of rendered resources. // It returns done=true only if all steps are executed and succeeded. ExecuteSteps(ctx context.Context, appRev *v1beta1.ApplicationRevision, taskRunners []types.TaskRunner) (state common.WorkflowState, err error) + // Trace record workflow state in controllerRevision. + Trace() error } diff --git a/pkg/workflow/recorder/recorder.go b/pkg/workflow/recorder/recorder.go new file mode 100644 index 000000000..0d1dd5b9b --- /dev/null +++ b/pkg/workflow/recorder/recorder.go @@ -0,0 +1,156 @@ +/*Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package recorder + +import ( + "context" + "fmt" + "sort" + "strings" + "time" + + "github.com/pkg/errors" + apps "k8s.io/api/apps/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" +) + +const ( + // LabelRecordSource is label that describe recorder source. + LabelRecordSource = "vela.io/source" + // LabelRecordVersion is label that describe recorder version. + LabelRecordVersion = "vela.io/wf-revision" +) + +type recorder struct { + cli client.Client + source *v1beta1.Application + err error +} + +// With a recorder store. +func With(cli client.Client, source *v1beta1.Application) Store { + return &recorder{ + cli: cli, + source: source, + } +} + +// Save object to controllerRevision. +func (r *recorder) Save(version string, data []byte) Store { + if r.err != nil { + return r + } + rv := &apps.ControllerRevision{} + rv.Namespace = r.source.GetNamespace() + rv.Revision = time.Now().UnixNano() + + if version == "" { + wfStatus := r.source.Status.Workflow + if wfStatus != nil { + if !strings.Contains(wfStatus.AppRevision, ":") { + version = wfStatus.AppRevision + } + } + } + + if version == "" { + version = fmt.Sprint(rv.Revision) + } + + rv.Name = fmt.Sprintf("record-%s-%s", r.source.Name, version) + + rv.SetLabels(map[string]string{ + LabelRecordSource: r.source.GetName(), + LabelRecordVersion: version, + }) + ownerRef := metav1.NewControllerRef(r.source, r.source.GroupVersionKind()) + ownerRef.APIVersion = v1beta1.SchemeGroupVersion.String() + ownerRef.Kind = v1beta1.ApplicationKind + rv.SetOwnerReferences([]metav1.OwnerReference{*ownerRef}) + rv.Data = runtime.RawExtension{ + Raw: data, + } + if err := r.cli.Create(context.Background(), rv); err != nil && !kerrors.IsAlreadyExists(err) { + r.err = errors.WithMessagef(err, "save record %s/%s", rv.Namespace, rv.Name) + } + return r +} + +// Limit gc over limit controllerRevisions. +func (r *recorder) Limit(max int) Store { + if r.err != nil { + return r + } + selector, err := labels.Parse(fmt.Sprintf("%s=%s", LabelRecordSource, r.source.GetName())) + if err != nil { + r.err = errors.WithMessagef(err, "limit recorder: make selector(source=%s)", r.source.GetName()) + return r + } + rds := &apps.ControllerRevisionList{} + if err := r.cli.List(context.Background(), rds, &client.ListOptions{ + LabelSelector: selector, + }); err != nil { + r.err = errors.WithMessagef(err, "limit recorder: list controllerRevision (source=%s)", r.source.GetName()) + } + if len(rds.Items) <= max { + return r + } + items := rds.Items + sort.Sort(rvSorter(items)) + for i := 0; i < len(rds.Items)-max; i++ { + o := &items[i] + if err := r.cli.Delete(context.Background(), o); err != nil { + r.err = errors.WithMessage(err, "limit recorder: delete controllerRevision") + break + } + } + return r +} + +// Error return error info. +func (r *recorder) Error() error { + return r.err +} + +// Store is an object that record info. +type Store interface { + Save(version string, data []byte) Store + Limit(max int) Store + Error() error +} + +type rvSorter []apps.ControllerRevision + +// Len is the number of elements in the collection. +func (rs rvSorter) Len() int { + return len(rs) +} + +// Less reports whether the element with index i must sort before the element with index j. +func (rs rvSorter) Less(i, j int) bool { + return rs[i].Revision < rs[j].Revision +} + +// Swap swaps the elements with indexes i and j. +func (rs rvSorter) Swap(i, j int) { + rs[i], rs[j] = rs[j], rs[i] +} diff --git a/pkg/workflow/recorder/recorder_test.go b/pkg/workflow/recorder/recorder_test.go new file mode 100644 index 000000000..d1c0b1d06 --- /dev/null +++ b/pkg/workflow/recorder/recorder_test.go @@ -0,0 +1,113 @@ +/*Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package recorder + +import ( + "context" + "fmt" + "testing" + + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/pkg/errors" + "gotest.tools/assert" + apps "k8s.io/api/apps/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" +) + +func TestRecord(t *testing.T) { + cli := makeMockClient() + app := &v1beta1.Application{} + app.Namespace = "default" + app.Name = "test-app" + data := []byte(`{"spec": "mock-data"}`) + err := With(cli, app).Save("v1", data). + Save("v2", data). + Save("v3", data).Limit(2).Error() + assert.NilError(t, err) + + crs := &apps.ControllerRevisionList{} + err = cli.List(context.Background(), crs) + assert.NilError(t, err) + assert.Equal(t, len(crs.Items), 2) + + assert.Equal(t, crs.Items[0].Name, "record-test-app-v2") + assert.Equal(t, crs.Items[1].Name, "record-test-app-v3") + creatErrorEnable = true + err = With(cli, app).Save("v1", data).Error() + assert.Equal(t, err.Error(), "save record default/record-test-app-v1: mock create error") + + creatErrorEnable = false + listErrorEnable = true + err = With(cli, app).Save("v1", data).Limit(3).Error() + assert.Equal(t, err.Error(), "limit recorder: list controllerRevision (source=test-app): mock list error") + + listErrorEnable = false + cli = makeMockClient() + err = With(cli, app).Save("", data).Limit(1).Error() + assert.NilError(t, err) + crs = &apps.ControllerRevisionList{} + err = cli.List(context.Background(), crs) + assert.NilError(t, err) + assert.Equal(t, crs.Items[0].Name, fmt.Sprintf("record-%s-%d", app.Name, crs.Items[0].Revision)) +} + +var ( + listErrorEnable bool + creatErrorEnable bool +) + +func makeMockClient() client.Client { + items := []apps.ControllerRevision{} + return &test.MockClient{ + MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + if listErrorEnable { + return errors.New("mock list error") + } + crList, ok := list.(*apps.ControllerRevisionList) + if ok { + *crList = apps.ControllerRevisionList{ + Items: items, + } + } + return nil + }, + MockCreate: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + if creatErrorEnable { + return errors.New("mock create error") + } + o, ok := obj.(*apps.ControllerRevision) + if ok { + items = append(items, *o) + } + return nil + }, + MockDelete: func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + o, ok := obj.(*apps.ControllerRevision) + if ok { + newItems := []apps.ControllerRevision{} + for index := range items { + if items[index].Name != o.Name || items[index].Namespace != o.Namespace { + newItems = append(newItems, items[index]) + } + } + items = newItems + } + return nil + }, + } +} diff --git a/pkg/workflow/types/types.go b/pkg/workflow/types/types.go index ce069ee64..be9aaf35f 100644 --- a/pkg/workflow/types/types.go +++ b/pkg/workflow/types/types.go @@ -77,4 +77,6 @@ type Action interface { const ( // ContextKeyMetadata is key that refer to application metadata. ContextKeyMetadata = "metadata__" + // AnnotationPublishVersion is annotation that record the application workflow version. + AnnotationPublishVersion = "vela.io/publish-version" ) diff --git a/pkg/workflow/workflow.go b/pkg/workflow/workflow.go index 16a69beb5..3bc279263 100644 --- a/pkg/workflow/workflow.go +++ b/pkg/workflow/workflow.go @@ -18,9 +18,12 @@ package workflow import ( "context" + "encoding/json" "fmt" + "time" "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/apis/core.oam.dev/common" @@ -29,6 +32,7 @@ import ( "github.com/oam-dev/kubevela/pkg/cue/model/value" "github.com/oam-dev/kubevela/pkg/oam/util" wfContext "github.com/oam-dev/kubevela/pkg/workflow/context" + "github.com/oam-dev/kubevela/pkg/workflow/recorder" wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types" ) @@ -62,29 +66,37 @@ func (w *workflow) ExecuteSteps(ctx context.Context, appRev *oamcore.Application } if w.app.Status.Workflow == nil || w.app.Status.Workflow.AppRevision != revAndSpecHash { + status := w.app.Status.Workflow + if status != nil && !status.Finished { + status.Terminated = true + return common.WorkflowStateTerminated, nil + } w.app.Status.Workflow = &common.WorkflowStatus{ AppRevision: revAndSpecHash, Mode: common.WorkflowModeStep, + StartTime: metav1.NewTime(time.Now()), } if w.dagMode { w.app.Status.Workflow.Mode = common.WorkflowModeDAG } - // clean recorded resources info. w.app.Status.Services = nil w.app.Status.AppliedResources = nil } wfStatus := w.app.Status.Workflow - allTasksDone := w.allDone(taskRunners) + if wfStatus.Finished { + return common.WorkflowStateFinished, nil + } if wfStatus.Terminated { return common.WorkflowStateTerminated, nil } if wfStatus.Suspend { return common.WorkflowStateSuspended, nil } + allTasksDone := w.allDone(taskRunners) if allTasksDone { - return common.WorkflowStateFinished, nil + return common.WorkflowStateSucceeded, nil } var ( @@ -112,11 +124,20 @@ func (w *workflow) ExecuteSteps(ctx context.Context, appRev *oamcore.Application return common.WorkflowStateSuspended, nil } if w.allDone(taskRunners) { - return common.WorkflowStateFinished, nil + return common.WorkflowStateSucceeded, nil } return common.WorkflowStateExecuting, nil } +// Trace record the workflow execute history. +func (w *workflow) Trace() error { + data, err := json.Marshal(w.app) + if err != nil { + return err + } + return recorder.With(w.cli, w.app).Save("", data).Limit(10).Error() +} + func (w *workflow) allDone(taskRunners []wfTypes.TaskRunner) bool { status := w.app.Status.Workflow for _, t := range taskRunners { @@ -285,15 +306,21 @@ func (e *engine) finishStep(operation *wfTypes.Operation) { } func (e *engine) updateStepStatus(status common.WorkflowStepStatus) { - var conditionUpdated bool + var ( + conditionUpdated bool + now = metav1.NewTime(time.Now()) + ) + status.LastExecuteTime = 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 conditionUpdated = true break } } if !conditionUpdated { + status.FirstExecuteTime = now e.status.Steps = append(e.status.Steps, status) } } @@ -303,6 +330,16 @@ func (e *engine) needStop() bool { } func computeAppRevisionHash(rev string, app *oamcore.Application) (string, error) { - specHash, err := utils.ComputeSpecHash(app.Spec) - return fmt.Sprintf("%s:%s", rev, specHash), err + version := "" + if annos := app.Annotations; annos != nil { + version = annos[wfTypes.AnnotationPublishVersion] + } + if version == "" { + specHash, err := utils.ComputeSpecHash(app.Spec) + if err != nil { + return "", err + } + version = fmt.Sprintf("%s:%s", rev, specHash) + } + return version, nil } diff --git a/pkg/workflow/workflow_test.go b/pkg/workflow/workflow_test.go index f88e9959f..bfa2f2317 100644 --- a/pkg/workflow/workflow_test.go +++ b/pkg/workflow/workflow_test.go @@ -72,6 +72,7 @@ var _ = Describe("Test Workflow", func() { workflowStatus := app.Status.Workflow Expect(workflowStatus.ContextBackend.Name).Should(BeEquivalentTo("workflow-" + app.Name + "-context")) workflowStatus.ContextBackend = nil + cleanStepTimeStamp(workflowStatus) Expect(cmp.Diff(*workflowStatus, common.WorkflowStatus{ AppRevision: workflowStatus.AppRevision, Mode: common.WorkflowModeStep, @@ -105,8 +106,13 @@ var _ = Describe("Test Workflow", func() { wf = NewWorkflow(app, k8sClient, common.WorkflowModeStep) state, err = wf.ExecuteSteps(context.Background(), revision, runners) Expect(err).ToNot(HaveOccurred()) - Expect(state).Should(BeEquivalentTo(common.WorkflowStateFinished)) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateTerminated)) + app.Status.Workflow.Finished = true + state, err = wf.ExecuteSteps(context.Background(), 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, @@ -148,6 +154,7 @@ var _ = Describe("Test Workflow", func() { Expect(state).Should(BeEquivalentTo(common.WorkflowStateSuspended)) wfStatus := *app.Status.Workflow wfStatus.ContextBackend = nil + cleanStepTimeStamp(&wfStatus) Expect(cmp.Diff(wfStatus, common.WorkflowStatus{ AppRevision: wfStatus.AppRevision, Mode: common.WorkflowModeStep, @@ -174,8 +181,9 @@ var _ = Describe("Test Workflow", func() { app.Labels = map[string]string{"for-test": "changed"} state, err = wf.ExecuteSteps(context.Background(), revision, runners) Expect(err).ToNot(HaveOccurred()) - Expect(state).Should(BeEquivalentTo(common.WorkflowStateFinished)) + 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, @@ -196,7 +204,7 @@ var _ = Describe("Test Workflow", func() { state, err = wf.ExecuteSteps(context.Background(), revision, runners) Expect(err).ToNot(HaveOccurred()) - Expect(state).Should(BeEquivalentTo(common.WorkflowStateFinished)) + Expect(state).Should(BeEquivalentTo(common.WorkflowStateSucceeded)) }) It("test for terminate", func() { @@ -215,6 +223,7 @@ var _ = Describe("Test Workflow", func() { 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, @@ -251,6 +260,7 @@ var _ = Describe("Test Workflow", func() { Expect(err).To(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, @@ -291,6 +301,7 @@ var _ = Describe("Test Workflow", func() { 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.WorkflowModeDAG, @@ -312,8 +323,9 @@ var _ = Describe("Test Workflow", func() { pending = false state, err = wf.ExecuteSteps(context.Background(), revision, runners) Expect(err).ToNot(HaveOccurred()) - Expect(state).Should(BeEquivalentTo(common.WorkflowStateFinished)) + 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.WorkflowModeDAG, @@ -463,3 +475,11 @@ func (tr *testTaskRunner) Run(ctx wfContext.Context, options *wfTypes.TaskRunOpt func (tr *testTaskRunner) Pending(ctx wfContext.Context) bool { return tr.checkPending(ctx) } + +func cleanStepTimeStamp(wfStatus *common.WorkflowStatus) { + wfStatus.StartTime = metav1.Time{} + for index := range wfStatus.Steps { + wfStatus.Steps[index].FirstExecuteTime = metav1.Time{} + wfStatus.Steps[index].LastExecuteTime = metav1.Time{} + } +}