Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhanced Different TTLSecondsAfterFinished depending on if job is in Succeeded, Failed or Error, Fixes #1883

Merged
merged 22 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,24 @@
}
}
},
"io.argoproj.workflow.v1alpha1.TTLStrategy": {
"description": "TTLStrategy is the strategy for the time to live depending on if the workflow succeded or failed",
"type": "object",
"properties": {
"secondsAfterCompleted": {
"type": "integer",
"format": "int32"
},
"secondsAfterFailed": {
"type": "integer",
"format": "int32"
},
"secondsAfterSuccess": {
"type": "integer",
"format": "int32"
}
}
},
"io.argoproj.workflow.v1alpha1.TarStrategy": {
"description": "TarStrategy will tar and gzip the file or directory when saving",
"type": "object"
Expand Down Expand Up @@ -1626,10 +1644,14 @@
"x-kubernetes-patch-strategy": "merge"
},
"ttlSecondsAfterFinished": {
"description": "TTLSecondsAfterFinished limits the lifetime of a Workflow that has finished execution (Succeeded, Failed, Error). If this field is set, once the Workflow finishes, it will be deleted after ttlSecondsAfterFinished expires. If this field is unset, ttlSecondsAfterFinished will not expire. If this field is set to zero, ttlSecondsAfterFinished expires immediately after the Workflow finishes.",
"description": "TTLSecondsAfterFinished limits the lifetime of a Workflow that has finished execution (Succeeded, Failed, Error). If this field is set, once the Workflow finishes, it will be deleted after ttlSecondsAfterFinished expires. If this field is unset, ttlSecondsAfterFinished will not expire. If this field is set to zero, ttlSecondsAfterFinished expires immediately after the Workflow finishes. DEPRECATED: Use TTLStrategy.SecondsAfterCompleted instead.",
"type": "integer",
"format": "int32"
},
"ttlStrategy": {
"description": "TTLStrategy limits the lifetime of a Workflow that has finished execution depending on if it Succeeded or Failed. If this struct is set, once the Workflow finishes, it will be deleted after the time to live expires. If this field is unset, the controller config map will hold the default values Update",
"$ref": "#/definitions/io.argoproj.workflow.v1alpha1.TTLStrategy"
},
"volumeClaimTemplates": {
"description": "VolumeClaimTemplates is a list of claims that containers are allowed to reference. The Workflow controller will create the claims at the beginning of the workflow and delete the claims upon completion of the workflow",
"type": "array",
Expand Down
11 changes: 8 additions & 3 deletions examples/gc-ttl.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# This example shows the ability to automatically delete workflows after a specified time period
# after the workflow completes. The ttlSecondsAfterFinished indicates the duration in seconds, after
# a workflow finishes, in which the workflow (and its pods) will be deleted.
# after the workflow completes. The TTLStrategy sets the strategy for how long workflows that are
# successful, not successful, or completed should live. The former ttlSecondsAfterFinished will be
# deprecated and will be replaced with TTLStrategy.SecondsAfterCompleted.
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: gc-ttl-
spec:
ttlSecondsAfterFinished: 10
TTLStrategy:
secondsAfterCompleted: 10 # Time to live after workflow is completed, replaces ttlSecondsAfterFinished
secondsAfterSuccess: 5 # Time to live after workflow is successful
secondsAfterFailed: 5 # Time to live after workflow fails
entrypoint: whalesay
templates:
- name: whalesay
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["hello world"]

955 changes: 600 additions & 355 deletions pkg/apis/workflow/v1alpha1/generated.pb.go

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions pkg/apis/workflow/v1alpha1/generated.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions pkg/apis/workflow/v1alpha1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion pkg/apis/workflow/v1alpha1/workflow_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"hash/fnv"

apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -106,6 +107,13 @@ type WorkflowList struct {
var _ TemplateGetter = &Workflow{}
var _ TemplateStorage = &Workflow{}

// TTLStrategy is the strategy for the time to live depending on if the workflow succeded or failed
type TTLStrategy struct {
NikeNano marked this conversation as resolved.
Show resolved Hide resolved
SecondsAfterCompleted *int32 `json:"secondsAfterCompleted,omitempty" protobuf:"bytes,1,opt,name=secondsAfterCompleted"`
SecondsAfterSuccess *int32 `json:"secondsAfterSuccess,omitempty" protobuf:"bytes,2,opt,name=secondsAfterSuccess"`
SecondsAfterFailed *int32 `json:"secondsAfterFailed,omitempty" protobuf:"bytes,3,opt,name=secondsAfterFailed"`
}

// WorkflowSpec is the specification of a Workflow.
type WorkflowSpec struct {
// Templates is a list of workflow templates used in a workflow
Expand Down Expand Up @@ -199,8 +207,16 @@ type WorkflowSpec struct {
// deleted after ttlSecondsAfterFinished expires. If this field is unset,
// ttlSecondsAfterFinished will not expire. If this field is set to zero,
// ttlSecondsAfterFinished expires immediately after the Workflow finishes.
// DEPRECATED: Use TTLStrategy.SecondsAfterCompleted instead.
TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty" protobuf:"bytes,18,opt,name=ttlSecondsAfterFinished"`
NikeNano marked this conversation as resolved.
Show resolved Hide resolved

// TTLStrategy limits the lifetime of a Workflow that has finished execution depending on if it
// Succeeded or Failed. If this struct is set, once the Workflow finishes, it will be
// deleted after the time to live expires. If this field is unset,
// the controller config map will hold the default values
// Update
TTLStrategy *TTLStrategy `json:"ttlStrategy,omitempty" protobuf:"bytes,30,opt,name=ttlStrategy"`

// Optional duration in seconds relative to the workflow start time which the workflow is
// allowed to run before the controller terminates the workflow. A value of zero is used to
// terminate a Running workflow
Expand Down Expand Up @@ -850,7 +866,7 @@ func isCompletedPhase(phase NodePhase) bool {
phase == NodeSkipped
}

// Remove returns whether or not the workflow has completed execution
// Completed returns whether or not the workflow has completed execution
func (ws *WorkflowStatus) Completed() bool {
return isCompletedPhase(ws.Phase)
}
Expand All @@ -860,6 +876,11 @@ func (ws *WorkflowStatus) Successful() bool {
return ws.Phase == NodeSucceeded
}

// Failed return whether or not the workflow has failed
func (ws *WorkflowStatus) Failed() bool {
return ws.Phase == NodeFailed
}

// Remove returns whether or not the node has completed execution
func (n NodeStatus) Completed() bool {
return isCompletedPhase(n.Phase) || n.IsDaemoned() && n.Phase != NodePending
Expand Down
36 changes: 36 additions & 0 deletions pkg/apis/workflow/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 27 additions & 7 deletions workflow/ttlcontroller/ttlcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,24 +199,44 @@ func (c *Controller) deleteWorkflow(key string) error {

func (c *Controller) ttlExpired(wf *wfv1.Workflow) bool {
// We don't care about the Workflows that are going to be deleted, or the ones that don't need clean up.
if wf.DeletionTimestamp != nil || wf.Spec.TTLSecondsAfterFinished == nil || wf.Status.FinishedAt.IsZero() {
if wf.DeletionTimestamp != nil || wf.Spec.TTLStrategy == nil || wf.Status.FinishedAt.IsZero() {
return false
}
now := c.clock.Now()
expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLSecondsAfterFinished))
return now.After(expiry)
if wf.Status.Failed() && wf.Spec.TTLStrategy != nil && wf.Spec.TTLStrategy.SecondsAfterFailed != nil {
expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLStrategy.SecondsAfterFailed))
return now.After(expiry)
} else if wf.Status.Successful() && wf.Spec.TTLStrategy != nil && wf.Spec.TTLStrategy.SecondsAfterSuccess != nil {
expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLStrategy.SecondsAfterSuccess))
return now.After(expiry)
} else {
expiry := wf.Status.FinishedAt.Add(time.Second * time.Duration(*wf.Spec.TTLStrategy.SecondsAfterCompleted))
return now.After(expiry)
}
}

func timeLeft(wf *wfv1.Workflow, since *time.Time) (*time.Duration, *time.Time) {
if wf.DeletionTimestamp != nil || wf.Spec.TTLSecondsAfterFinished == nil || wf.Status.FinishedAt.IsZero() {
if wf.DeletionTimestamp != nil || wf.Spec.TTLStrategy == nil || wf.Status.FinishedAt.IsZero() {
return nil, nil
}
sinceUTC := since.UTC()
finishAtUTC := wf.Status.FinishedAt.UTC()
if finishAtUTC.After(sinceUTC) {
log.Infof("Warning: Found Workflow %s/%s finished in the future. This is likely due to time skew in the cluster. Workflow cleanup will be deferred.", wf.Namespace, wf.Name)
}
expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLSecondsAfterFinished) * time.Second)
remaining := expireAtUTC.Sub(sinceUTC)
return &remaining, &expireAtUTC
if wf.Status.Failed() && wf.Spec.TTLStrategy.SecondsAfterFailed != nil {
expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLStrategy.SecondsAfterFailed) * time.Second)
remaining := expireAtUTC.Sub(sinceUTC)
return &remaining, &expireAtUTC
} else if wf.Status.Successful() && wf.Spec.TTLStrategy.SecondsAfterSuccess != nil {
expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLStrategy.SecondsAfterSuccess) * time.Second)
remaining := expireAtUTC.Sub(sinceUTC)
return &remaining, &expireAtUTC
} else if wf.Spec.TTLStrategy.SecondsAfterCompleted != nil {
expireAtUTC := finishAtUTC.Add(time.Duration(*wf.Spec.TTLStrategy.SecondsAfterCompleted) * time.Second)
remaining := expireAtUTC.Sub(sinceUTC)
return &remaining, &expireAtUTC
} else {
return nil, nil
}
}
Loading