From 209b32684ea76c5a49ebc68b9990746bbd42c414 Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Tue, 24 Jul 2018 15:04:13 -0700 Subject: [PATCH 1/7] Add TTLAfterFinished alpha feature --- pkg/apis/batch/types.go | 12 ++++++++++++ pkg/apis/batch/v1/conversion.go | 2 ++ pkg/features/kube_features.go | 7 +++++++ staging/src/k8s.io/api/batch/v1/types.go | 12 ++++++++++++ 4 files changed, 33 insertions(+) diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go index 32d921fb6af..d62cdaf3bbb 100644 --- a/pkg/apis/batch/types.go +++ b/pkg/apis/batch/types.go @@ -138,6 +138,18 @@ type JobSpec struct { // Describes the pod that will be created when executing a job. Template api.PodTemplateSpec + + // ttlSecondsAfterFinished limits the lifetime of a Job that has finished + // execution (either Complete or Failed). If this field is set, + // ttlSecondsAfterFinished after the Job finishes, it is eligible to be + // automatically deleted. When the Job is being deleted, its lifecycle + // guarantees (e.g. finalizers) will be honored. If this field is unset, + // the Job won't be automatically deleted. If this field is set to zero, + // the Job becomes eligible to be deleted immediately after it finishes. + // This field is alpha-level and is only honored by servers that enable the + // TTLAfterFinished feature. + // +optional + TTLSecondsAfterFinished *int32 } // JobStatus represents the current state of a Job. diff --git a/pkg/apis/batch/v1/conversion.go b/pkg/apis/batch/v1/conversion.go index 9c20fa93702..7c715542367 100644 --- a/pkg/apis/batch/v1/conversion.go +++ b/pkg/apis/batch/v1/conversion.go @@ -54,6 +54,7 @@ func Convert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *batchv1.JobSpec out.Completions = in.Completions out.ActiveDeadlineSeconds = in.ActiveDeadlineSeconds out.BackoffLimit = in.BackoffLimit + out.TTLSecondsAfterFinished = in.TTLSecondsAfterFinished out.Selector = in.Selector if in.ManualSelector != nil { out.ManualSelector = new(bool) @@ -73,6 +74,7 @@ func Convert_v1_JobSpec_To_batch_JobSpec(in *batchv1.JobSpec, out *batch.JobSpec out.Completions = in.Completions out.ActiveDeadlineSeconds = in.ActiveDeadlineSeconds out.BackoffLimit = in.BackoffLimit + out.TTLSecondsAfterFinished = in.TTLSecondsAfterFinished out.Selector = in.Selector if in.ManualSelector != nil { out.ManualSelector = new(bool) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 9fdefcf71d5..56a7ed33e7d 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -381,6 +381,12 @@ const ( // // Enables control over ProcMountType for containers. ProcMountType utilfeature.Feature = "ProcMountType" + + // owner: @janetkuo + // alpha: v1.12 + // + // Allow TTL controller to clean up Pods and Jobs after they finish. + TTLAfterFinished utilfeature.Feature = "TTLAfterFinished" ) func init() { @@ -445,6 +451,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS SCTPSupport: {Default: false, PreRelease: utilfeature.Alpha}, VolumeSnapshotDataSource: {Default: false, PreRelease: utilfeature.Alpha}, ProcMountType: {Default: false, PreRelease: utilfeature.Alpha}, + TTLAfterFinished: {Default: false, PreRelease: utilfeature.Alpha}, // inherited features from generic apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: diff --git a/staging/src/k8s.io/api/batch/v1/types.go b/staging/src/k8s.io/api/batch/v1/types.go index 84abb1a9015..e9fa61cd045 100644 --- a/staging/src/k8s.io/api/batch/v1/types.go +++ b/staging/src/k8s.io/api/batch/v1/types.go @@ -114,6 +114,18 @@ type JobSpec struct { // Describes the pod that will be created when executing a job. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,6,opt,name=template"` + + // ttlSecondsAfterFinished limits the lifetime of a Job that has finished + // execution (either Complete or Failed). If this field is set, + // ttlSecondsAfterFinished after the Job finishes, it is eligible to be + // automatically deleted. When the Job is being deleted, its lifecycle + // guarantees (e.g. finalizers) will be honored. If this field is unset, + // the Job won't be automatically deleted. If this field is set to zero, + // the Job becomes eligible to be deleted immediately after it finishes. + // This field is alpha-level and is only honored by servers that enable the + // TTLAfterFinished feature. + // +optional + TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty"` } // JobStatus represents the current state of a Job. From 1d6dd864070f6497708ee129e5afafcd3193b759 Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Fri, 17 Aug 2018 13:33:03 -0700 Subject: [PATCH 2/7] Autogen API docs --- api/openapi-spec/swagger.json | 5 + api/swagger-spec/batch_v1.json | 5 + api/swagger-spec/batch_v1beta1.json | 5 + api/swagger-spec/batch_v2alpha1.json | 5 + docs/api-reference/batch/v1/definitions.html | 7 + .../batch/v1beta1/definitions.html | 7 + .../batch/v2alpha1/definitions.html | 7 + pkg/apis/batch/v1/zz_generated.conversion.go | 2 + pkg/apis/batch/zz_generated.deepcopy.go | 5 + .../src/k8s.io/api/batch/v1/generated.pb.go | 146 +++++++++++------- .../src/k8s.io/api/batch/v1/generated.proto | 12 ++ staging/src/k8s.io/api/batch/v1/types.go | 2 +- .../batch/v1/types_swagger_doc_generated.go | 17 +- .../api/batch/v1/zz_generated.deepcopy.go | 5 + 14 files changed, 164 insertions(+), 66 deletions(-) diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 5142b1ff06e..53d1c1cc535 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -79306,6 +79306,11 @@ "template": { "description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", "$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec" + }, + "ttlSecondsAfterFinished": { + "description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.", + "type": "integer", + "format": "int32" } } }, diff --git a/api/swagger-spec/batch_v1.json b/api/swagger-spec/batch_v1.json index 4669972e178..1c7f36766f6 100644 --- a/api/swagger-spec/batch_v1.json +++ b/api/swagger-spec/batch_v1.json @@ -1536,6 +1536,11 @@ "template": { "$ref": "v1.PodTemplateSpec", "description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/" + }, + "ttlSecondsAfterFinished": { + "type": "integer", + "format": "int32", + "description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature." } } }, diff --git a/api/swagger-spec/batch_v1beta1.json b/api/swagger-spec/batch_v1beta1.json index 3487425e287..b43f6107c15 100644 --- a/api/swagger-spec/batch_v1beta1.json +++ b/api/swagger-spec/batch_v1beta1.json @@ -1591,6 +1591,11 @@ "template": { "$ref": "v1.PodTemplateSpec", "description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/" + }, + "ttlSecondsAfterFinished": { + "type": "integer", + "format": "int32", + "description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature." } } }, diff --git a/api/swagger-spec/batch_v2alpha1.json b/api/swagger-spec/batch_v2alpha1.json index ff9d51385b1..044d864f92a 100644 --- a/api/swagger-spec/batch_v2alpha1.json +++ b/api/swagger-spec/batch_v2alpha1.json @@ -1591,6 +1591,11 @@ "template": { "$ref": "v1.PodTemplateSpec", "description": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/" + }, + "ttlSecondsAfterFinished": { + "type": "integer", + "format": "int32", + "description": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature." } } }, diff --git a/docs/api-reference/batch/v1/definitions.html b/docs/api-reference/batch/v1/definitions.html index 14b6e918979..dcd059bbc4d 100755 --- a/docs/api-reference/batch/v1/definitions.html +++ b/docs/api-reference/batch/v1/definitions.html @@ -2505,6 +2505,13 @@ When an object is created, the system will populate this list with the current s

v1.PodTemplateSpec

+ +

ttlSecondsAfterFinished

+

ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won’t be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.

+

false

+

integer (int32)

+ + diff --git a/docs/api-reference/batch/v1beta1/definitions.html b/docs/api-reference/batch/v1beta1/definitions.html index 148aa8bcf8e..c3bca7454ef 100755 --- a/docs/api-reference/batch/v1beta1/definitions.html +++ b/docs/api-reference/batch/v1beta1/definitions.html @@ -2539,6 +2539,13 @@ When an object is created, the system will populate this list with the current s

v1.PodTemplateSpec

+ +

ttlSecondsAfterFinished

+

ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won’t be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.

+

false

+

integer (int32)

+ + diff --git a/docs/api-reference/batch/v2alpha1/definitions.html b/docs/api-reference/batch/v2alpha1/definitions.html index 950d2d5cf86..51a06d431be 100755 --- a/docs/api-reference/batch/v2alpha1/definitions.html +++ b/docs/api-reference/batch/v2alpha1/definitions.html @@ -2512,6 +2512,13 @@ When an object is created, the system will populate this list with the current s

v1.PodTemplateSpec

+ +

ttlSecondsAfterFinished

+

ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won’t be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.

+

false

+

integer (int32)

+ + diff --git a/pkg/apis/batch/v1/zz_generated.conversion.go b/pkg/apis/batch/v1/zz_generated.conversion.go index 55a56426e1d..066f30ee30b 100644 --- a/pkg/apis/batch/v1/zz_generated.conversion.go +++ b/pkg/apis/batch/v1/zz_generated.conversion.go @@ -217,6 +217,7 @@ func autoConvert_v1_JobSpec_To_batch_JobSpec(in *v1.JobSpec, out *batch.JobSpec, if err := apiscorev1.Convert_v1_PodTemplateSpec_To_core_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } + out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished)) return nil } @@ -230,6 +231,7 @@ func autoConvert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *v1.JobSpec, if err := apiscorev1.Convert_core_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } + out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished)) return nil } diff --git a/pkg/apis/batch/zz_generated.deepcopy.go b/pkg/apis/batch/zz_generated.deepcopy.go index ea986d3d7cb..492b1699e9e 100644 --- a/pkg/apis/batch/zz_generated.deepcopy.go +++ b/pkg/apis/batch/zz_generated.deepcopy.go @@ -262,6 +262,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) { **out = **in } in.Template.DeepCopyInto(&out.Template) + if in.TTLSecondsAfterFinished != nil { + in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished + *out = new(int32) + **out = **in + } return } diff --git a/staging/src/k8s.io/api/batch/v1/generated.pb.go b/staging/src/k8s.io/api/batch/v1/generated.pb.go index aa5cdf89b68..097a6ff28c5 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v1/generated.pb.go @@ -276,6 +276,11 @@ func (m *JobSpec) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(*m.BackoffLimit)) } + if m.TTLSecondsAfterFinished != nil { + dAtA[i] = 0x40 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.TTLSecondsAfterFinished)) + } return i, nil } @@ -433,6 +438,9 @@ func (m *JobSpec) Size() (n int) { if m.BackoffLimit != nil { n += 1 + sovGenerated(uint64(*m.BackoffLimit)) } + if m.TTLSecondsAfterFinished != nil { + n += 1 + sovGenerated(uint64(*m.TTLSecondsAfterFinished)) + } return n } @@ -522,6 +530,7 @@ func (this *JobSpec) String() string { `ManualSelector:` + valueToStringGenerated(this.ManualSelector) + `,`, `Template:` + strings.Replace(strings.Replace(this.Template.String(), "PodTemplateSpec", "k8s_io_api_core_v1.PodTemplateSpec", 1), `&`, ``, 1) + `,`, `BackoffLimit:` + valueToStringGenerated(this.BackoffLimit) + `,`, + `TTLSecondsAfterFinished:` + valueToStringGenerated(this.TTLSecondsAfterFinished) + `,`, `}`, }, "") return s @@ -1219,6 +1228,26 @@ func (m *JobSpec) Unmarshal(dAtA []byte) error { } } m.BackoffLimit = &v + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TTLSecondsAfterFinished", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.TTLSecondsAfterFinished = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -1554,61 +1583,64 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 893 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x41, 0x6f, 0xe3, 0x44, - 0x18, 0x8d, 0x9b, 0xa6, 0x4d, 0x26, 0x69, 0xb7, 0x0c, 0xaa, 0x14, 0x2a, 0xe4, 0x2c, 0x41, 0x42, - 0x05, 0x09, 0x9b, 0x94, 0x0a, 0x21, 0x04, 0x48, 0xb8, 0x68, 0x25, 0xaa, 0x54, 0x5b, 0x26, 0x45, - 0x48, 0x08, 0x24, 0xc6, 0xf6, 0x97, 0xd4, 0xc4, 0xf6, 0x58, 0x9e, 0x49, 0xa4, 0xde, 0xf8, 0x09, - 0xfc, 0x08, 0xc4, 0x4f, 0x41, 0x3d, 0xee, 0x71, 0x4f, 0x11, 0x35, 0xdc, 0xb9, 0xef, 0x09, 0xcd, - 0x78, 0x62, 0x3b, 0x6d, 0x2a, 0xda, 0xbd, 0x79, 0xde, 0xbc, 0xf7, 0xbe, 0xf1, 0x37, 0x6f, 0x3e, - 0xf4, 0xf9, 0xf4, 0x53, 0x6e, 0x05, 0xcc, 0x9e, 0xce, 0x5c, 0x48, 0x63, 0x10, 0xc0, 0xed, 0x39, - 0xc4, 0x3e, 0x4b, 0x6d, 0xbd, 0x41, 0x93, 0xc0, 0x76, 0xa9, 0xf0, 0x2e, 0xed, 0xf9, 0xc0, 0x9e, - 0x40, 0x0c, 0x29, 0x15, 0xe0, 0x5b, 0x49, 0xca, 0x04, 0xc3, 0x6f, 0xe6, 0x24, 0x8b, 0x26, 0x81, - 0xa5, 0x48, 0xd6, 0x7c, 0x70, 0xf0, 0xe1, 0x24, 0x10, 0x97, 0x33, 0xd7, 0xf2, 0x58, 0x64, 0x4f, - 0xd8, 0x84, 0xd9, 0x8a, 0xeb, 0xce, 0xc6, 0x6a, 0xa5, 0x16, 0xea, 0x2b, 0xf7, 0x38, 0xe8, 0x57, - 0x0a, 0x79, 0x2c, 0x85, 0x35, 0x75, 0x0e, 0x8e, 0x4b, 0x4e, 0x44, 0xbd, 0xcb, 0x20, 0x86, 0xf4, - 0xca, 0x4e, 0xa6, 0x13, 0x09, 0x70, 0x3b, 0x02, 0x41, 0xd7, 0xa9, 0xec, 0xfb, 0x54, 0xe9, 0x2c, - 0x16, 0x41, 0x04, 0x77, 0x04, 0x9f, 0xfc, 0x9f, 0x80, 0x7b, 0x97, 0x10, 0xd1, 0xdb, 0xba, 0xfe, - 0xbf, 0x06, 0xaa, 0x9f, 0x32, 0x17, 0xff, 0x8c, 0x9a, 0xf2, 0x2c, 0x3e, 0x15, 0xb4, 0x6b, 0x3c, - 0x35, 0x0e, 0xdb, 0x47, 0x1f, 0x59, 0x65, 0x87, 0x0a, 0x4b, 0x2b, 0x99, 0x4e, 0x24, 0xc0, 0x2d, - 0xc9, 0xb6, 0xe6, 0x03, 0xeb, 0xb9, 0xfb, 0x0b, 0x78, 0xe2, 0x0c, 0x04, 0x75, 0xf0, 0xf5, 0xa2, - 0x57, 0xcb, 0x16, 0x3d, 0x54, 0x62, 0xa4, 0x70, 0xc5, 0x5f, 0xa2, 0x4d, 0x9e, 0x80, 0xd7, 0xdd, - 0x50, 0xee, 0x6f, 0x5b, 0x6b, 0xfa, 0x6f, 0x9d, 0x32, 0x77, 0x94, 0x80, 0xe7, 0x74, 0xb4, 0xd3, - 0xa6, 0x5c, 0x11, 0xa5, 0xc3, 0xcf, 0xd0, 0x16, 0x17, 0x54, 0xcc, 0x78, 0xb7, 0xae, 0x1c, 0xcc, - 0x7b, 0x1d, 0x14, 0xcb, 0xd9, 0xd5, 0x1e, 0x5b, 0xf9, 0x9a, 0x68, 0x75, 0xff, 0xcf, 0x3a, 0xea, - 0x9c, 0x32, 0xf7, 0x84, 0xc5, 0x7e, 0x20, 0x02, 0x16, 0xe3, 0x63, 0xb4, 0x29, 0xae, 0x12, 0x50, - 0xbf, 0xdd, 0x72, 0x9e, 0x2e, 0x4b, 0x5f, 0x5c, 0x25, 0xf0, 0x6a, 0xd1, 0xdb, 0xab, 0x72, 0x25, - 0x46, 0x14, 0x1b, 0x0f, 0x8b, 0xe3, 0x6c, 0x28, 0xdd, 0xf1, 0x6a, 0xb9, 0x57, 0x8b, 0xde, 0x9a, - 0x74, 0x58, 0x85, 0xd3, 0xea, 0xa1, 0xf0, 0x04, 0xed, 0x84, 0x94, 0x8b, 0xf3, 0x94, 0xb9, 0x70, - 0x11, 0x44, 0xa0, 0xff, 0xf1, 0x83, 0x87, 0xdd, 0x81, 0x54, 0x38, 0xfb, 0xfa, 0x00, 0x3b, 0xc3, - 0xaa, 0x11, 0x59, 0xf5, 0xc5, 0x73, 0x84, 0x25, 0x70, 0x91, 0xd2, 0x98, 0xe7, 0xbf, 0x24, 0xab, - 0x6d, 0x3e, 0xba, 0xda, 0x81, 0xae, 0x86, 0x87, 0x77, 0xdc, 0xc8, 0x9a, 0x0a, 0xf8, 0x3d, 0xb4, - 0x95, 0x02, 0xe5, 0x2c, 0xee, 0x36, 0x54, 0xbb, 0x8a, 0xdb, 0x21, 0x0a, 0x25, 0x7a, 0x17, 0xbf, - 0x8f, 0xb6, 0x23, 0xe0, 0x9c, 0x4e, 0xa0, 0xbb, 0xa5, 0x88, 0x4f, 0x34, 0x71, 0xfb, 0x2c, 0x87, - 0xc9, 0x72, 0xbf, 0xff, 0x87, 0x81, 0xb6, 0x4f, 0x99, 0x3b, 0x0c, 0xb8, 0xc0, 0x3f, 0xde, 0x89, - 0xaf, 0xf5, 0xb0, 0x9f, 0x91, 0x6a, 0x15, 0xde, 0x3d, 0x5d, 0xa7, 0xb9, 0x44, 0x2a, 0xd1, 0xfd, - 0x02, 0x35, 0x02, 0x01, 0x91, 0xbc, 0xea, 0xfa, 0x61, 0xfb, 0xa8, 0x7b, 0x5f, 0xf2, 0x9c, 0x1d, - 0x6d, 0xd2, 0xf8, 0x46, 0xd2, 0x49, 0xae, 0xea, 0xff, 0x53, 0x57, 0x07, 0x95, 0x59, 0xc6, 0x03, - 0xd4, 0x4e, 0x68, 0x4a, 0xc3, 0x10, 0xc2, 0x80, 0x47, 0xea, 0xac, 0x0d, 0xe7, 0x49, 0xb6, 0xe8, - 0xb5, 0xcf, 0x4b, 0x98, 0x54, 0x39, 0x52, 0xe2, 0xb1, 0x28, 0x09, 0x41, 0x36, 0x33, 0x8f, 0x9b, - 0x96, 0x9c, 0x94, 0x30, 0xa9, 0x72, 0xf0, 0x73, 0xb4, 0x4f, 0x3d, 0x11, 0xcc, 0xe1, 0x6b, 0xa0, - 0x7e, 0x18, 0xc4, 0x30, 0x02, 0x8f, 0xc5, 0x7e, 0xfe, 0x74, 0xea, 0xce, 0x5b, 0xd9, 0xa2, 0xb7, - 0xff, 0xd5, 0x3a, 0x02, 0x59, 0xaf, 0xc3, 0x3f, 0xa1, 0x26, 0x87, 0x10, 0x3c, 0xc1, 0x52, 0x1d, - 0x96, 0x8f, 0x1f, 0xd8, 0x5f, 0xea, 0x42, 0x38, 0xd2, 0x52, 0xa7, 0x23, 0x1b, 0xbc, 0x5c, 0x91, - 0xc2, 0x12, 0x7f, 0x86, 0x76, 0x23, 0x1a, 0xcf, 0x68, 0xc1, 0x54, 0x29, 0x69, 0x3a, 0x38, 0x5b, - 0xf4, 0x76, 0xcf, 0x56, 0x76, 0xc8, 0x2d, 0x26, 0xfe, 0x16, 0x35, 0x05, 0x44, 0x49, 0x48, 0x45, - 0x1e, 0x99, 0xf6, 0xd1, 0xbb, 0xd5, 0xfb, 0x91, 0x2f, 0x4f, 0x1e, 0xe4, 0x9c, 0xf9, 0x17, 0x9a, - 0xa6, 0x46, 0x4c, 0x71, 0xdf, 0x4b, 0x94, 0x14, 0x36, 0xf8, 0x18, 0x75, 0x5c, 0xea, 0x4d, 0xd9, - 0x78, 0x3c, 0x0c, 0xa2, 0x40, 0x74, 0xb7, 0x55, 0xcb, 0xf7, 0xb2, 0x45, 0xaf, 0xe3, 0x54, 0x70, - 0xb2, 0xc2, 0xea, 0xff, 0x5e, 0x47, 0xad, 0x62, 0xfc, 0xe0, 0xef, 0x10, 0xf2, 0x96, 0x8f, 0x9d, - 0x77, 0x0d, 0x15, 0x9c, 0x77, 0xee, 0x0b, 0x4e, 0x31, 0x16, 0xca, 0x19, 0x5a, 0x40, 0x9c, 0x54, - 0x8c, 0xf0, 0xf7, 0xa8, 0xc5, 0x05, 0x4d, 0x85, 0x7a, 0xb6, 0x1b, 0x8f, 0x7e, 0xb6, 0x3b, 0xd9, - 0xa2, 0xd7, 0x1a, 0x2d, 0x0d, 0x48, 0xe9, 0x85, 0xc7, 0x68, 0xb7, 0x4c, 0xd0, 0x6b, 0x8e, 0x20, - 0x75, 0x5d, 0x27, 0x2b, 0x2e, 0xe4, 0x96, 0xab, 0x1c, 0x04, 0x79, 0xc4, 0x54, 0x8e, 0x1a, 0xe5, - 0x20, 0xc8, 0xf3, 0x48, 0xf4, 0x2e, 0xb6, 0x51, 0x8b, 0xcf, 0x3c, 0x0f, 0xc0, 0x07, 0x5f, 0xa5, - 0xa1, 0xe1, 0xbc, 0xa1, 0xa9, 0xad, 0xd1, 0x72, 0x83, 0x94, 0x1c, 0x69, 0x3c, 0xa6, 0x41, 0x08, - 0xbe, 0x4a, 0x41, 0xc5, 0xf8, 0x99, 0x42, 0x89, 0xde, 0x75, 0x0e, 0xaf, 0x6f, 0xcc, 0xda, 0x8b, - 0x1b, 0xb3, 0xf6, 0xf2, 0xc6, 0xac, 0xfd, 0x9a, 0x99, 0xc6, 0x75, 0x66, 0x1a, 0x2f, 0x32, 0xd3, - 0x78, 0x99, 0x99, 0xc6, 0x5f, 0x99, 0x69, 0xfc, 0xf6, 0xb7, 0x59, 0xfb, 0x61, 0x63, 0x3e, 0xf8, - 0x2f, 0x00, 0x00, 0xff, 0xff, 0xdd, 0xcc, 0x84, 0xd1, 0x61, 0x08, 0x00, 0x00, + // 929 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0x5d, 0x6f, 0xe3, 0x44, + 0x14, 0xad, 0x9b, 0xa6, 0x4d, 0xa6, 0x1f, 0x5b, 0x06, 0x55, 0x1b, 0x0a, 0xb2, 0x97, 0x20, 0xa1, + 0x82, 0x84, 0x4d, 0x4b, 0x85, 0x10, 0x02, 0xa4, 0x75, 0x51, 0x25, 0xaa, 0x54, 0x5b, 0x26, 0x59, + 0x21, 0x21, 0x90, 0x18, 0xdb, 0x37, 0x89, 0x89, 0xed, 0xb1, 0x3c, 0x93, 0x48, 0x7d, 0xe3, 0x27, + 0xf0, 0x23, 0x10, 0x7f, 0x82, 0x77, 0xd4, 0xc7, 0x7d, 0xdc, 0x27, 0x8b, 0x9a, 0x1f, 0xc0, 0xfb, + 0x3e, 0xa1, 0x19, 0x3b, 0xb6, 0xd3, 0x26, 0xa2, 0xcb, 0x5b, 0xe6, 0xcc, 0x39, 0xe7, 0x5e, 0xdf, + 0x39, 0xb9, 0xe8, 0x8b, 0xc9, 0x67, 0xdc, 0xf4, 0x99, 0x35, 0x99, 0x3a, 0x90, 0x44, 0x20, 0x80, + 0x5b, 0x33, 0x88, 0x3c, 0x96, 0x58, 0xc5, 0x05, 0x8d, 0x7d, 0xcb, 0xa1, 0xc2, 0x1d, 0x5b, 0xb3, + 0x63, 0x6b, 0x04, 0x11, 0x24, 0x54, 0x80, 0x67, 0xc6, 0x09, 0x13, 0x0c, 0xbf, 0x99, 0x93, 0x4c, + 0x1a, 0xfb, 0xa6, 0x22, 0x99, 0xb3, 0xe3, 0xc3, 0x8f, 0x46, 0xbe, 0x18, 0x4f, 0x1d, 0xd3, 0x65, + 0xa1, 0x35, 0x62, 0x23, 0x66, 0x29, 0xae, 0x33, 0x1d, 0xaa, 0x93, 0x3a, 0xa8, 0x5f, 0xb9, 0xc7, + 0x61, 0xb7, 0x56, 0xc8, 0x65, 0x09, 0x2c, 0xa9, 0x73, 0x78, 0x5a, 0x71, 0x42, 0xea, 0x8e, 0xfd, + 0x08, 0x92, 0x6b, 0x2b, 0x9e, 0x8c, 0x24, 0xc0, 0xad, 0x10, 0x04, 0x5d, 0xa6, 0xb2, 0x56, 0xa9, + 0x92, 0x69, 0x24, 0xfc, 0x10, 0xee, 0x09, 0x3e, 0xfd, 0x2f, 0x01, 0x77, 0xc7, 0x10, 0xd2, 0xbb, + 0xba, 0xee, 0x3f, 0x1a, 0x6a, 0x5c, 0x30, 0x07, 0xff, 0x84, 0x5a, 0xb2, 0x17, 0x8f, 0x0a, 0xda, + 0xd1, 0x9e, 0x68, 0x47, 0xdb, 0x27, 0x1f, 0x9b, 0xd5, 0x84, 0x4a, 0x4b, 0x33, 0x9e, 0x8c, 0x24, + 0xc0, 0x4d, 0xc9, 0x36, 0x67, 0xc7, 0xe6, 0x33, 0xe7, 0x67, 0x70, 0xc5, 0x25, 0x08, 0x6a, 0xe3, + 0x9b, 0xd4, 0x58, 0xcb, 0x52, 0x03, 0x55, 0x18, 0x29, 0x5d, 0xf1, 0x57, 0x68, 0x83, 0xc7, 0xe0, + 0x76, 0xd6, 0x95, 0xfb, 0x3b, 0xe6, 0x92, 0xf9, 0x9b, 0x17, 0xcc, 0xe9, 0xc7, 0xe0, 0xda, 0x3b, + 0x85, 0xd3, 0x86, 0x3c, 0x11, 0xa5, 0xc3, 0xe7, 0x68, 0x93, 0x0b, 0x2a, 0xa6, 0xbc, 0xd3, 0x50, + 0x0e, 0xfa, 0x4a, 0x07, 0xc5, 0xb2, 0xf7, 0x0a, 0x8f, 0xcd, 0xfc, 0x4c, 0x0a, 0x75, 0xf7, 0xcf, + 0x06, 0xda, 0xb9, 0x60, 0xce, 0x19, 0x8b, 0x3c, 0x5f, 0xf8, 0x2c, 0xc2, 0xa7, 0x68, 0x43, 0x5c, + 0xc7, 0xa0, 0x3e, 0xbb, 0x6d, 0x3f, 0x99, 0x97, 0x1e, 0x5c, 0xc7, 0xf0, 0x2a, 0x35, 0xf6, 0xeb, + 0x5c, 0x89, 0x11, 0xc5, 0xc6, 0xbd, 0xb2, 0x9d, 0x75, 0xa5, 0x3b, 0x5d, 0x2c, 0xf7, 0x2a, 0x35, + 0x96, 0xa4, 0xc3, 0x2c, 0x9d, 0x16, 0x9b, 0xc2, 0x23, 0xb4, 0x1b, 0x50, 0x2e, 0xae, 0x12, 0xe6, + 0xc0, 0xc0, 0x0f, 0xa1, 0xf8, 0xc6, 0x0f, 0x1f, 0xf6, 0x06, 0x52, 0x61, 0x1f, 0x14, 0x0d, 0xec, + 0xf6, 0xea, 0x46, 0x64, 0xd1, 0x17, 0xcf, 0x10, 0x96, 0xc0, 0x20, 0xa1, 0x11, 0xcf, 0x3f, 0x49, + 0x56, 0xdb, 0x78, 0xed, 0x6a, 0x87, 0x45, 0x35, 0xdc, 0xbb, 0xe7, 0x46, 0x96, 0x54, 0xc0, 0xef, + 0xa3, 0xcd, 0x04, 0x28, 0x67, 0x51, 0xa7, 0xa9, 0xc6, 0x55, 0xbe, 0x0e, 0x51, 0x28, 0x29, 0x6e, + 0xf1, 0x07, 0x68, 0x2b, 0x04, 0xce, 0xe9, 0x08, 0x3a, 0x9b, 0x8a, 0xf8, 0xa8, 0x20, 0x6e, 0x5d, + 0xe6, 0x30, 0x99, 0xdf, 0x77, 0x7f, 0xd7, 0xd0, 0xd6, 0x05, 0x73, 0x7a, 0x3e, 0x17, 0xf8, 0x87, + 0x7b, 0xf1, 0x35, 0x1f, 0xf6, 0x31, 0x52, 0xad, 0xc2, 0xbb, 0x5f, 0xd4, 0x69, 0xcd, 0x91, 0x5a, + 0x74, 0xbf, 0x44, 0x4d, 0x5f, 0x40, 0x28, 0x9f, 0xba, 0x71, 0xb4, 0x7d, 0xd2, 0x59, 0x95, 0x3c, + 0x7b, 0xb7, 0x30, 0x69, 0x7e, 0x23, 0xe9, 0x24, 0x57, 0x75, 0xff, 0xd8, 0x50, 0x8d, 0xca, 0x2c, + 0xe3, 0x63, 0xb4, 0x1d, 0xd3, 0x84, 0x06, 0x01, 0x04, 0x3e, 0x0f, 0x55, 0xaf, 0x4d, 0xfb, 0x51, + 0x96, 0x1a, 0xdb, 0x57, 0x15, 0x4c, 0xea, 0x1c, 0x29, 0x71, 0x59, 0x18, 0x07, 0x20, 0x87, 0x99, + 0xc7, 0xad, 0x90, 0x9c, 0x55, 0x30, 0xa9, 0x73, 0xf0, 0x33, 0x74, 0x40, 0x5d, 0xe1, 0xcf, 0xe0, + 0x6b, 0xa0, 0x5e, 0xe0, 0x47, 0xd0, 0x07, 0x97, 0x45, 0x5e, 0xfe, 0xd7, 0x69, 0xd8, 0x6f, 0x65, + 0xa9, 0x71, 0xf0, 0x74, 0x19, 0x81, 0x2c, 0xd7, 0xe1, 0x1f, 0x51, 0x8b, 0x43, 0x00, 0xae, 0x60, + 0x49, 0x11, 0x96, 0x4f, 0x1e, 0x38, 0x5f, 0xea, 0x40, 0xd0, 0x2f, 0xa4, 0xf6, 0x8e, 0x1c, 0xf0, + 0xfc, 0x44, 0x4a, 0x4b, 0xfc, 0x39, 0xda, 0x0b, 0x69, 0x34, 0xa5, 0x25, 0x53, 0xa5, 0xa4, 0x65, + 0xe3, 0x2c, 0x35, 0xf6, 0x2e, 0x17, 0x6e, 0xc8, 0x1d, 0x26, 0xfe, 0x16, 0xb5, 0x04, 0x84, 0x71, + 0x40, 0x45, 0x1e, 0x99, 0xed, 0x93, 0xf7, 0xea, 0xef, 0x23, 0xff, 0x79, 0xb2, 0x91, 0x2b, 0xe6, + 0x0d, 0x0a, 0x9a, 0x5a, 0x31, 0xe5, 0x7b, 0xcf, 0x51, 0x52, 0xda, 0xe0, 0x53, 0xb4, 0xe3, 0x50, + 0x77, 0xc2, 0x86, 0xc3, 0x9e, 0x1f, 0xfa, 0xa2, 0xb3, 0xa5, 0x46, 0xbe, 0x9f, 0xa5, 0xc6, 0x8e, + 0x5d, 0xc3, 0xc9, 0x02, 0x0b, 0x3f, 0x47, 0x8f, 0x85, 0x08, 0x8a, 0x89, 0x3d, 0x1d, 0x0a, 0x48, + 0xce, 0xfd, 0xc8, 0xe7, 0x63, 0xf0, 0x3a, 0x2d, 0x65, 0xf0, 0x76, 0x96, 0x1a, 0x8f, 0x07, 0x83, + 0xde, 0x32, 0x0a, 0x59, 0xa5, 0xed, 0xfe, 0xd6, 0x40, 0xed, 0x72, 0xab, 0xe1, 0xe7, 0x08, 0xb9, + 0xf3, 0x1d, 0xc2, 0x3b, 0x9a, 0xca, 0xe3, 0xbb, 0xab, 0xf2, 0x58, 0x6e, 0x9b, 0x6a, 0x35, 0x97, + 0x10, 0x27, 0x35, 0x23, 0xfc, 0x1d, 0x6a, 0x73, 0x41, 0x13, 0xa1, 0xb6, 0xc1, 0xfa, 0x6b, 0x6f, + 0x83, 0xdd, 0x2c, 0x35, 0xda, 0xfd, 0xb9, 0x01, 0xa9, 0xbc, 0xf0, 0x10, 0xed, 0x55, 0xc1, 0xfc, + 0x9f, 0x9b, 0x4d, 0xa5, 0xe0, 0x6c, 0xc1, 0x85, 0xdc, 0x71, 0x95, 0xfb, 0x25, 0x4f, 0xae, 0x8a, + 0x67, 0xb3, 0xda, 0x2f, 0x79, 0xcc, 0x49, 0x71, 0x8b, 0x2d, 0xd4, 0xe6, 0x53, 0xd7, 0x05, 0xf0, + 0xc0, 0x53, 0x21, 0x6b, 0xda, 0x6f, 0x14, 0xd4, 0x76, 0x7f, 0x7e, 0x41, 0x2a, 0x8e, 0x34, 0x1e, + 0x52, 0x3f, 0x00, 0x4f, 0x85, 0xab, 0x66, 0x7c, 0xae, 0x50, 0x52, 0xdc, 0xda, 0x47, 0x37, 0xb7, + 0xfa, 0xda, 0x8b, 0x5b, 0x7d, 0xed, 0xe5, 0xad, 0xbe, 0xf6, 0x4b, 0xa6, 0x6b, 0x37, 0x99, 0xae, + 0xbd, 0xc8, 0x74, 0xed, 0x65, 0xa6, 0x6b, 0x7f, 0x65, 0xba, 0xf6, 0xeb, 0xdf, 0xfa, 0xda, 0xf7, + 0xeb, 0xb3, 0xe3, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x13, 0xdb, 0x98, 0xf9, 0xb8, 0x08, 0x00, + 0x00, } diff --git a/staging/src/k8s.io/api/batch/v1/generated.proto b/staging/src/k8s.io/api/batch/v1/generated.proto index 91858b019b4..039149daba2 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1/generated.proto @@ -134,6 +134,18 @@ message JobSpec { // Describes the pod that will be created when executing a job. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ optional k8s.io.api.core.v1.PodTemplateSpec template = 6; + + // ttlSecondsAfterFinished limits the lifetime of a Job that has finished + // execution (either Complete or Failed). If this field is set, + // ttlSecondsAfterFinished after the Job finishes, it is eligible to be + // automatically deleted. When the Job is being deleted, its lifecycle + // guarantees (e.g. finalizers) will be honored. If this field is unset, + // the Job won't be automatically deleted. If this field is set to zero, + // the Job becomes eligible to be deleted immediately after it finishes. + // This field is alpha-level and is only honored by servers that enable the + // TTLAfterFinished feature. + // +optional + optional int32 ttlSecondsAfterFinished = 8; } // JobStatus represents the current state of a Job. diff --git a/staging/src/k8s.io/api/batch/v1/types.go b/staging/src/k8s.io/api/batch/v1/types.go index e9fa61cd045..8dad9043d5c 100644 --- a/staging/src/k8s.io/api/batch/v1/types.go +++ b/staging/src/k8s.io/api/batch/v1/types.go @@ -125,7 +125,7 @@ type JobSpec struct { // This field is alpha-level and is only honored by servers that enable the // TTLAfterFinished feature. // +optional - TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty"` + TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty" protobuf:"varint,8,opt,name=ttlSecondsAfterFinished"` } // JobStatus represents the current state of a Job. diff --git a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go index 2bb794a5fe1..d8e2bdd780f 100644 --- a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go @@ -63,14 +63,15 @@ func (JobList) SwaggerDoc() map[string]string { } var map_JobSpec = map[string]string{ - "": "JobSpec describes how the job execution will look like.", - "parallelism": "Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", - "completions": "Specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", - "activeDeadlineSeconds": "Specifies the duration in seconds relative to the startTime that the job may be active before the system tries to terminate it; value must be positive integer", - "backoffLimit": "Specifies the number of retries before marking this job failed. Defaults to 6", - "selector": "A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", - "manualSelector": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector", - "template": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", + "": "JobSpec describes how the job execution will look like.", + "parallelism": "Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", + "completions": "Specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", + "activeDeadlineSeconds": "Specifies the duration in seconds relative to the startTime that the job may be active before the system tries to terminate it; value must be positive integer", + "backoffLimit": "Specifies the number of retries before marking this job failed. Defaults to 6", + "selector": "A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + "manualSelector": "manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector", + "template": "Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/", + "ttlSecondsAfterFinished": "ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature.", } func (JobSpec) SwaggerDoc() map[string]string { diff --git a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go index d641cf3080e..88cb01678be 100644 --- a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go @@ -138,6 +138,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) { **out = **in } in.Template.DeepCopyInto(&out.Template) + if in.TTLSecondsAfterFinished != nil { + in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished + *out = new(int32) + **out = **in + } return } From 518680758746b8190137b9677bb5ca9bbcdc2d4e Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Wed, 25 Jul 2018 15:49:04 -0700 Subject: [PATCH 3/7] Add TTL GC controller --- cmd/kube-controller-manager/app/BUILD | 1 + .../app/controllermanager.go | 1 + cmd/kube-controller-manager/app/core.go | 12 + pkg/controller/BUILD | 1 + pkg/controller/ttlafterfinished/BUILD | 54 +++ .../ttlafterfinished_controller.go | 308 ++++++++++++++++++ .../ttlafterfinished_controller_test.go | 177 ++++++++++ .../rbac/bootstrappolicy/controller_policy.go | 9 + 8 files changed, 563 insertions(+) create mode 100644 pkg/controller/ttlafterfinished/BUILD create mode 100644 pkg/controller/ttlafterfinished/ttlafterfinished_controller.go create mode 100644 pkg/controller/ttlafterfinished/ttlafterfinished_controller_test.go diff --git a/cmd/kube-controller-manager/app/BUILD b/cmd/kube-controller-manager/app/BUILD index bbdcb19a044..f183dda468d 100644 --- a/cmd/kube-controller-manager/app/BUILD +++ b/cmd/kube-controller-manager/app/BUILD @@ -68,6 +68,7 @@ go_library( "//pkg/controller/serviceaccount:go_default_library", "//pkg/controller/statefulset:go_default_library", "//pkg/controller/ttl:go_default_library", + "//pkg/controller/ttlafterfinished:go_default_library", "//pkg/controller/volume/attachdetach:go_default_library", "//pkg/controller/volume/expand:go_default_library", "//pkg/controller/volume/persistentvolume:go_default_library", diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 12e7c35faea..1d0a19e759d 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -378,6 +378,7 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController controllers["pvc-protection"] = startPVCProtectionController controllers["pv-protection"] = startPVProtectionController + controllers["ttl-after-finished-controller"] = startTTLAfterFinishedController return controllers } diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index 92700988766..202a7f9668b 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -52,6 +52,7 @@ import ( servicecontroller "k8s.io/kubernetes/pkg/controller/service" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl" + "k8s.io/kubernetes/pkg/controller/ttlafterfinished" "k8s.io/kubernetes/pkg/controller/volume/attachdetach" "k8s.io/kubernetes/pkg/controller/volume/expand" persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume" @@ -417,3 +418,14 @@ func startPVProtectionController(ctx ControllerContext) (http.Handler, bool, err ).Run(1, ctx.Stop) return nil, true, nil } + +func startTTLAfterFinishedController(ctx ControllerContext) (http.Handler, bool, error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + return nil, false, nil + } + go ttlafterfinished.New( + ctx.InformerFactory.Batch().V1().Jobs(), + ctx.ClientBuilder.ClientOrDie("ttl-after-finished-controller"), + ).Run(5, ctx.Stop) + return nil, true, nil +} diff --git a/pkg/controller/BUILD b/pkg/controller/BUILD index 17f4fba639b..e5c8dc7695f 100644 --- a/pkg/controller/BUILD +++ b/pkg/controller/BUILD @@ -131,6 +131,7 @@ filegroup( "//pkg/controller/statefulset:all-srcs", "//pkg/controller/testutil:all-srcs", "//pkg/controller/ttl:all-srcs", + "//pkg/controller/ttlafterfinished:all-srcs", "//pkg/controller/util/node:all-srcs", "//pkg/controller/volume/attachdetach:all-srcs", "//pkg/controller/volume/events:all-srcs", diff --git a/pkg/controller/ttlafterfinished/BUILD b/pkg/controller/ttlafterfinished/BUILD new file mode 100644 index 00000000000..3eda34912f6 --- /dev/null +++ b/pkg/controller/ttlafterfinished/BUILD @@ -0,0 +1,54 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["ttlafterfinished_controller.go"], + importpath = "k8s.io/kubernetes/pkg/controller/ttlafterfinished", + visibility = ["//visibility:public"], + deps = [ + "//pkg/controller:go_default_library", + "//pkg/controller/job:go_default_library", + "//pkg/kubectl/scheme:go_default_library", + "//pkg/util/metrics:go_default_library", + "//staging/src/k8s.io/api/batch/v1:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/clock:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//staging/src/k8s.io/client-go/informers/batch/v1:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/listers/batch/v1:go_default_library", + "//staging/src/k8s.io/client-go/tools/cache:go_default_library", + "//staging/src/k8s.io/client-go/tools/record:go_default_library", + "//staging/src/k8s.io/client-go/util/workqueue:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["ttlafterfinished_controller_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/api/batch/v1:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/ttlafterfinished/ttlafterfinished_controller.go b/pkg/controller/ttlafterfinished/ttlafterfinished_controller.go new file mode 100644 index 00000000000..99c5e625fdc --- /dev/null +++ b/pkg/controller/ttlafterfinished/ttlafterfinished_controller.go @@ -0,0 +1,308 @@ +/* +Copyright 2018 The Kubernetes 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 ttlafterfinished + +import ( + "fmt" + "time" + + "github.com/golang/glog" + + batch "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/clock" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + batchinformers "k8s.io/client-go/informers/batch/v1" + clientset "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + batchlisters "k8s.io/client-go/listers/batch/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller" + jobutil "k8s.io/kubernetes/pkg/controller/job" + "k8s.io/kubernetes/pkg/kubectl/scheme" + "k8s.io/kubernetes/pkg/util/metrics" +) + +// Controller watches for changes of Jobs API objects. Triggered by Job creation +// and updates, it enqueues Jobs that have non-nil `.spec.ttlSecondsAfterFinished` +// to the `queue`. The Controller has workers who consume `queue`, check whether +// the Job TTL has expired or not; if the Job TTL hasn't expired, it will add the +// Job to the queue after the TTL is expected to expire; if the TTL has expired, the +// worker will send requests to the API server to delete the Jobs accordingly. +// This is implemented outside of Job controller for separation of concerns, and +// because it will be extended to handle other finishable resource types. +type Controller struct { + client clientset.Interface + recorder record.EventRecorder + + // jLister can list/get Jobs from the shared informer's store + jLister batchlisters.JobLister + + // jStoreSynced returns true if the Job store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + jListerSynced cache.InformerSynced + + // Jobs that the controller will check its TTL and attempt to delete when the TTL expires. + queue workqueue.RateLimitingInterface + + // The clock for tracking time + clock clock.Clock +} + +// New creates an instance of Controller +func New(jobInformer batchinformers.JobInformer, client clientset.Interface) *Controller { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")}) + + if client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil { + metrics.RegisterMetricAndTrackRateLimiterUsage("ttl_after_finished_controller", client.CoreV1().RESTClient().GetRateLimiter()) + } + + tc := &Controller{ + client: client, + recorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "ttl-after-finished-controller"}), + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ttl_jobs_to_delete"), + } + + jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: tc.addJob, + UpdateFunc: tc.updateJob, + }) + + tc.jLister = jobInformer.Lister() + tc.jListerSynced = jobInformer.Informer().HasSynced + + tc.clock = clock.RealClock{} + + return tc +} + +// Run starts the workers to clean up Jobs. +func (tc *Controller) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer tc.queue.ShutDown() + + glog.Infof("Starting TTL after finished controller") + defer glog.Infof("Shutting down TTL after finished controller") + + if !controller.WaitForCacheSync("TTL after finished", stopCh, tc.jListerSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(tc.worker, time.Second, stopCh) + } + + <-stopCh +} + +func (tc *Controller) addJob(obj interface{}) { + job := obj.(*batch.Job) + glog.V(4).Infof("Adding job %s/%s", job.Namespace, job.Name) + + if job.DeletionTimestamp == nil && needsCleanup(job) { + tc.enqueue(job) + } +} + +func (tc *Controller) updateJob(old, cur interface{}) { + job := cur.(*batch.Job) + glog.V(4).Infof("Updating job %s/%s", job.Namespace, job.Name) + + if job.DeletionTimestamp == nil && needsCleanup(job) { + tc.enqueue(job) + } +} + +func (tc *Controller) enqueue(job *batch.Job) { + glog.V(4).Infof("Add job %s/%s to cleanup", job.Namespace, job.Name) + key, err := controller.KeyFunc(job) + if err != nil { + utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", job, err)) + return + } + + tc.queue.Add(key) +} + +func (tc *Controller) enqueueAfter(job *batch.Job, after time.Duration) { + key, err := controller.KeyFunc(job) + if err != nil { + utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", job, err)) + return + } + + tc.queue.AddAfter(key, after) +} + +func (tc *Controller) worker() { + for tc.processNextWorkItem() { + } +} + +func (tc *Controller) processNextWorkItem() bool { + key, quit := tc.queue.Get() + if quit { + return false + } + defer tc.queue.Done(key) + + err := tc.processJob(key.(string)) + tc.handleErr(err, key) + + return true +} + +func (tc *Controller) handleErr(err error, key interface{}) { + if err == nil { + tc.queue.Forget(key) + return + } + + utilruntime.HandleError(fmt.Errorf("error cleaning up Job %v, will retry: %v", key, err)) + tc.queue.AddRateLimited(key) +} + +// processJob will check the Job's state and TTL and delete the Job when it +// finishes and its TTL after finished has expired. If the Job hasn't finished or +// its TTL hasn't expired, it will be added to the queue after the TTL is expected +// to expire. +// This function is not meant to be invoked concurrently with the same key. +func (tc *Controller) processJob(key string) error { + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return err + } + + glog.V(4).Infof("Checking if Job %s/%s is ready for cleanup", namespace, name) + // Ignore the Jobs that are already deleted or being deleted, or the ones that don't need clean up. + job, err := tc.jLister.Jobs(namespace).Get(name) + if errors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + + if expired, err := tc.processTTL(job); err != nil { + return err + } else if !expired { + return nil + } + + // The Job's TTL is assumed to have expired, but the Job TTL might be stale. + // Before deleting the Job, do a final sanity check. + // If TTL is modified before we do this check, we cannot be sure if the TTL truly expires. + // The latest Job may have a different UID, but it's fine because the checks will be run again. + fresh, err := tc.client.BatchV1().Jobs(namespace).Get(name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + // Use the latest Job TTL to see if the TTL truly expires. + if expired, err := tc.processTTL(fresh); err != nil { + return err + } else if !expired { + return nil + } + // Cascade deletes the Jobs if TTL truly expires. + policy := metav1.DeletePropagationForeground + options := &metav1.DeleteOptions{ + PropagationPolicy: &policy, + Preconditions: &metav1.Preconditions{UID: &fresh.UID}, + } + glog.V(4).Infof("Cleaning up Job %s/%s", namespace, name) + return tc.client.BatchV1().Jobs(fresh.Namespace).Delete(fresh.Name, options) +} + +// processTTL checks whether a given Job's TTL has expired, and add it to the queue after the TTL is expected to expire +// if the TTL will expire later. +func (tc *Controller) processTTL(job *batch.Job) (expired bool, err error) { + // We don't care about the Jobs that are going to be deleted, or the ones that don't need clean up. + if job.DeletionTimestamp != nil || !needsCleanup(job) { + return false, nil + } + + now := tc.clock.Now() + t, err := timeLeft(job, &now) + if err != nil { + return false, err + } + + // TTL has expired + if *t <= 0 { + return true, nil + } + + tc.enqueueAfter(job, *t) + return false, nil +} + +// needsCleanup checks whether a Job has finished and has a TTL set. +func needsCleanup(j *batch.Job) bool { + return j.Spec.TTLSecondsAfterFinished != nil && jobutil.IsJobFinished(j) +} + +func getFinishAndExpireTime(j *batch.Job) (*time.Time, *time.Time, error) { + if !needsCleanup(j) { + return nil, nil, fmt.Errorf("Job %s/%s should not be cleaned up", j.Namespace, j.Name) + } + finishAt, err := jobFinishTime(j) + if err != nil { + return nil, nil, err + } + finishAtUTC := finishAt.UTC() + expireAtUTC := finishAtUTC.Add(time.Duration(*j.Spec.TTLSecondsAfterFinished) * time.Second) + return &finishAtUTC, &expireAtUTC, nil +} + +func timeLeft(j *batch.Job, since *time.Time) (*time.Duration, error) { + finishAt, expireAt, err := getFinishAndExpireTime(j) + if err != nil { + return nil, err + } + if finishAt.UTC().After(since.UTC()) { + glog.Warningf("Warning: Found Job %s/%s finished in the future. This is likely due to time skew in the cluster. Job cleanup will be deferred.", j.Namespace, j.Name) + } + remaining := expireAt.UTC().Sub(since.UTC()) + glog.V(4).Infof("Found Job %s/%s finished at %v, remaining TTL %v since %v, TTL will expire at %v", j.Namespace, j.Name, finishAt.UTC(), remaining, since.UTC(), expireAt.UTC()) + return &remaining, nil +} + +// jobFinishTime takes an already finished Job and returns the time it finishes. +func jobFinishTime(finishedJob *batch.Job) (metav1.Time, error) { + for _, c := range finishedJob.Status.Conditions { + if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue { + finishAt := c.LastTransitionTime + if finishAt.IsZero() { + return metav1.Time{}, fmt.Errorf("unable to find the time when the Job %s/%s finished", finishedJob.Namespace, finishedJob.Name) + } + return c.LastTransitionTime, nil + } + } + + // This should never happen if the Jobs has finished + return metav1.Time{}, fmt.Errorf("unable to find the status of the finished Job %s/%s", finishedJob.Namespace, finishedJob.Name) +} diff --git a/pkg/controller/ttlafterfinished/ttlafterfinished_controller_test.go b/pkg/controller/ttlafterfinished/ttlafterfinished_controller_test.go new file mode 100644 index 00000000000..b5908955aa1 --- /dev/null +++ b/pkg/controller/ttlafterfinished/ttlafterfinished_controller_test.go @@ -0,0 +1,177 @@ +/* +Copyright 2018 The Kubernetes 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 ttlafterfinished + +import ( + "strings" + "testing" + "time" + + batch "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func newJob(completionTime, failedTime metav1.Time, ttl *int32) *batch.Job { + j := &batch.Job{ + TypeMeta: metav1.TypeMeta{Kind: "Job"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: metav1.NamespaceDefault, + }, + Spec: batch.JobSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Image: "foo/bar"}, + }, + }, + }, + }, + } + + if !completionTime.IsZero() { + c := batch.JobCondition{Type: batch.JobComplete, Status: v1.ConditionTrue, LastTransitionTime: completionTime} + j.Status.Conditions = append(j.Status.Conditions, c) + } + + if !failedTime.IsZero() { + c := batch.JobCondition{Type: batch.JobFailed, Status: v1.ConditionTrue, LastTransitionTime: failedTime} + j.Status.Conditions = append(j.Status.Conditions, c) + } + + if ttl != nil { + j.Spec.TTLSecondsAfterFinished = ttl + } + + return j +} + +func durationPointer(n int) *time.Duration { + s := time.Duration(n) * time.Second + return &s +} + +func int32Ptr(n int32) *int32 { + return &n +} + +func TestTimeLeft(t *testing.T) { + now := metav1.Now() + + testCases := []struct { + name string + completionTime metav1.Time + failedTime metav1.Time + ttl *int32 + since *time.Time + expectErr bool + expectErrStr string + expectedTimeLeft *time.Duration + }{ + { + name: "Error case: Job unfinished", + ttl: int32Ptr(100), + since: &now.Time, + expectErr: true, + expectErrStr: "should not be cleaned up", + }, + { + name: "Error case: Job completed now, no TTL", + completionTime: now, + since: &now.Time, + expectErr: true, + expectErrStr: "should not be cleaned up", + }, + { + name: "Job completed now, 0s TTL", + completionTime: now, + ttl: int32Ptr(0), + since: &now.Time, + expectedTimeLeft: durationPointer(0), + }, + { + name: "Job completed now, 10s TTL", + completionTime: now, + ttl: int32Ptr(10), + since: &now.Time, + expectedTimeLeft: durationPointer(10), + }, + { + name: "Job completed 10s ago, 15s TTL", + completionTime: metav1.NewTime(now.Add(-10 * time.Second)), + ttl: int32Ptr(15), + since: &now.Time, + expectedTimeLeft: durationPointer(5), + }, + { + name: "Error case: Job failed now, no TTL", + failedTime: now, + since: &now.Time, + expectErr: true, + expectErrStr: "should not be cleaned up", + }, + { + name: "Job failed now, 0s TTL", + failedTime: now, + ttl: int32Ptr(0), + since: &now.Time, + expectedTimeLeft: durationPointer(0), + }, + { + name: "Job failed now, 10s TTL", + failedTime: now, + ttl: int32Ptr(10), + since: &now.Time, + expectedTimeLeft: durationPointer(10), + }, + { + name: "Job failed 10s ago, 15s TTL", + failedTime: metav1.NewTime(now.Add(-10 * time.Second)), + ttl: int32Ptr(15), + since: &now.Time, + expectedTimeLeft: durationPointer(5), + }, + } + + for _, tc := range testCases { + job := newJob(tc.completionTime, tc.failedTime, tc.ttl) + gotTimeLeft, gotErr := timeLeft(job, tc.since) + if tc.expectErr != (gotErr != nil) { + t.Errorf("%s: expected error is %t, got %t, error: %v", tc.name, tc.expectErr, gotErr != nil, gotErr) + } + if tc.expectErr && len(tc.expectErrStr) == 0 { + t.Errorf("%s: invalid test setup; error message must not be empty for error cases", tc.name) + } + if tc.expectErr && !strings.Contains(gotErr.Error(), tc.expectErrStr) { + t.Errorf("%s: expected error message contains %q, got %v", tc.name, tc.expectErrStr, gotErr) + } + if !tc.expectErr { + if *gotTimeLeft != *tc.expectedTimeLeft { + t.Errorf("%s: expected time left %v, got %v", tc.name, tc.expectedTimeLeft, gotTimeLeft) + } + } + } +} diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index 5dfbaa6f23b..ca81758fea4 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -340,6 +340,15 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) eventsRule(), }, }) + if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ttl-after-finished-controller"}, + Rules: []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("get", "list", "watch", "delete").Groups(batchGroup).Resources("jobs").RuleOrDie(), + eventsRule(), + }, + }) + } return controllerRoles, controllerRoleBindings } From 47d06c446d60d23ffc206eae31387403e8346680 Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Fri, 3 Aug 2018 15:05:56 -0700 Subject: [PATCH 4/7] Validate Job .spec.ttlSecondsAfterFinished; clear it when feature disabled 1. If TTLAfterFinished feature is enabled, the value should be non-negative. 2. If TTLAfterFinished feature is disabled, the field value should not be kept. --- pkg/apis/batch/validation/BUILD | 4 ++ pkg/apis/batch/validation/validation.go | 10 +++ pkg/apis/batch/validation/validation_test.go | 76 +++++++++++++++++--- pkg/registry/batch/job/BUILD | 4 ++ pkg/registry/batch/job/strategy.go | 11 +++ pkg/registry/batch/job/strategy_test.go | 33 +++++++-- 6 files changed, 123 insertions(+), 15 deletions(-) diff --git a/pkg/apis/batch/validation/BUILD b/pkg/apis/batch/validation/BUILD index b71dec02963..d371966a354 100644 --- a/pkg/apis/batch/validation/BUILD +++ b/pkg/apis/batch/validation/BUILD @@ -14,11 +14,13 @@ go_library( "//pkg/apis/batch:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/core/validation:go_default_library", + "//pkg/features:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/github.com/robfig/cron:go_default_library", ], ) @@ -30,8 +32,10 @@ go_test( deps = [ "//pkg/apis/batch:go_default_library", "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index a31bf6416e4..1e04e9779e5 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -24,9 +24,11 @@ import ( "k8s.io/apimachinery/pkg/labels" apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/kubernetes/pkg/features" ) // TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and @@ -117,6 +119,14 @@ func validateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList { if spec.BackoffLimit != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.BackoffLimit), fldPath.Child("backoffLimit"))...) } + if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + // normal validation for TTLSecondsAfterFinished + if spec.TTLSecondsAfterFinished != nil { + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.TTLSecondsAfterFinished), fldPath.Child("ttlSecondsAfterFinished"))...) + } + } else if spec.TTLSecondsAfterFinished != nil { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("ttlSecondsAfterFinished"), "disabled by feature-gate")) + } allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...) if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index 13135e2ea53..7ff9e3f73ea 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -17,13 +17,16 @@ limitations under the License. package validation import ( + "fmt" "strings" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" ) func getValidManualSelector() *metav1.LabelSelector { @@ -64,7 +67,21 @@ func getValidPodTemplateSpecForGenerated(selector *metav1.LabelSelector) api.Pod } } +func featureToggle(feature utilfeature.Feature) []string { + enabled := fmt.Sprintf("%s=%t", feature, true) + disabled := fmt.Sprintf("%s=%t", feature, false) + return []string{enabled, disabled} +} + func TestValidateJob(t *testing.T) { + ttlEnabled := utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) + defer func() { + err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%t", features.TTLAfterFinished, ttlEnabled)) + if err != nil { + t.Fatalf("Failed to set feature gate for %s: %v", features.TTLAfterFinished, err) + } + }() + validManualSelector := getValidManualSelector() validPodTemplateSpecForManual := getValidPodTemplateSpecForManual(validManualSelector) validGeneratedSelector := getValidGeneratedSelector() @@ -214,15 +231,39 @@ func TestValidateJob(t *testing.T) { }, } - for k, v := range errorCases { - errs := ValidateJob(&v) - if len(errs) == 0 { - t.Errorf("expected failure for %s", k) + for _, setFeature := range featureToggle(features.TTLAfterFinished) { + // Set error cases based on if TTLAfterFinished feature is enabled or not + if err := utilfeature.DefaultFeatureGate.Set(setFeature); err != nil { + t.Fatalf("Failed to set feature gate for %s: %v", features.TTLAfterFinished, err) + } + ttlCase := "spec.ttlSecondsAfterFinished:must be greater than or equal to 0" + if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + errorCases[ttlCase] = batch.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.JobSpec{ + TTLSecondsAfterFinished: &negative, + Selector: validGeneratedSelector, + Template: validPodTemplateSpecForGenerated, + }, + } } else { - s := strings.Split(k, ":") - err := errs[0] - if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { - t.Errorf("unexpected error: %v, expected: %s", err, k) + delete(errorCases, ttlCase) + } + + for k, v := range errorCases { + errs := ValidateJob(&v) + if len(errs) == 0 { + t.Errorf("expected failure for %s", k) + } else { + s := strings.Split(k, ":") + err := errs[0] + if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { + t.Errorf("unexpected error: %v, expected: %s", err, k) + } } } } @@ -584,6 +625,25 @@ func TestValidateCronJob(t *testing.T) { }, }, } + if utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + errorCases["spec.jobTemplate.spec.ttlSecondsAfterFinished:must be greater than or equal to 0"] = batch.CronJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "* * * * ?", + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + TTLSecondsAfterFinished: &negative, + Template: validPodTemplateSpec, + }, + }, + }, + } + } for k, v := range errorCases { errs := ValidateCronJob(&v) diff --git a/pkg/registry/batch/job/BUILD b/pkg/registry/batch/job/BUILD index 5f2ccc0ab99..30474db1f3c 100644 --- a/pkg/registry/batch/job/BUILD +++ b/pkg/registry/batch/job/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/api/pod:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/batch/validation:go_default_library", + "//pkg/features:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", @@ -27,6 +28,7 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library", "//staging/src/k8s.io/apiserver/pkg/storage:go_default_library", "//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) @@ -39,10 +41,12 @@ go_test( "//pkg/api/testing:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/core:go_default_library", + "//pkg/features:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", ], ) diff --git a/pkg/registry/batch/job/strategy.go b/pkg/registry/batch/job/strategy.go index c0eb2be5f53..4e46ddc4b37 100644 --- a/pkg/registry/batch/job/strategy.go +++ b/pkg/registry/batch/job/strategy.go @@ -30,10 +30,12 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch/validation" + "k8s.io/kubernetes/pkg/features" ) // jobStrategy implements verification logic for Replication Controllers. @@ -61,6 +63,10 @@ func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { job := obj.(*batch.Job) job.Status = batch.JobStatus{} + if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + job.Spec.TTLSecondsAfterFinished = nil + } + pod.DropDisabledAlphaFields(&job.Spec.Template.Spec) } @@ -70,6 +76,11 @@ func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object oldJob := old.(*batch.Job) newJob.Status = oldJob.Status + if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + newJob.Spec.TTLSecondsAfterFinished = nil + oldJob.Spec.TTLSecondsAfterFinished = nil + } + pod.DropDisabledAlphaFields(&newJob.Spec.Template.Spec) pod.DropDisabledAlphaFields(&oldJob.Spec.Template.Spec) } diff --git a/pkg/registry/batch/job/strategy_test.go b/pkg/registry/batch/job/strategy_test.go index f8cfc609196..e3b51678c15 100644 --- a/pkg/registry/batch/job/strategy_test.go +++ b/pkg/registry/batch/job/strategy_test.go @@ -24,19 +24,24 @@ import ( "k8s.io/apimachinery/pkg/types" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" ) func newBool(a bool) *bool { - r := new(bool) - *r = a - return r + return &a +} + +func newInt32(i int32) *int32 { + return &i } func TestJobStrategy(t *testing.T) { + ttlEnabled := utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) ctx := genericapirequest.NewDefaultContext() if !Strategy.NamespaceScoped() { t.Errorf("Job must be namespace scoped") @@ -64,9 +69,10 @@ func TestJobStrategy(t *testing.T) { Namespace: metav1.NamespaceDefault, }, Spec: batch.JobSpec{ - Selector: validSelector, - Template: validPodTemplateSpec, - ManualSelector: newBool(true), + Selector: validSelector, + Template: validPodTemplateSpec, + TTLSecondsAfterFinished: newInt32(0), // Set TTL + ManualSelector: newBool(true), }, Status: batch.JobStatus{ Active: 11, @@ -81,11 +87,21 @@ func TestJobStrategy(t *testing.T) { if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } + if ttlEnabled && job.Spec.TTLSecondsAfterFinished == nil { + // When the TTL feature is enabled, the TTL field can be set + t.Errorf("Job should allow setting .spec.ttlSecondsAfterFinished when %v feature is enabled", features.TTLAfterFinished) + } + if !ttlEnabled && job.Spec.TTLSecondsAfterFinished != nil { + // When the TTL feature is disabled, the TTL field cannot be set + t.Errorf("Job should not allow setting .spec.ttlSecondsAfterFinished when %v feature is disabled", features.TTLAfterFinished) + } + parallelism := int32(10) updatedJob := &batch.Job{ ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "4"}, Spec: batch.JobSpec{ - Parallelism: ¶llelism, + Parallelism: ¶llelism, + TTLSecondsAfterFinished: newInt32(1), // Update TTL }, Status: batch.JobStatus{ Active: 11, @@ -101,6 +117,9 @@ func TestJobStrategy(t *testing.T) { if len(errs) == 0 { t.Errorf("Expected a validation error") } + if ttlEnabled != (job.Spec.TTLSecondsAfterFinished != nil || updatedJob.Spec.TTLSecondsAfterFinished != nil) { + t.Errorf("Job should only allow updating .spec.ttlSecondsAfterFinished when %v feature is enabled", features.TTLAfterFinished) + } // Make sure we correctly implement the interface. // Otherwise a typo could silently change the default. From 0a6389e872562714a5041785725525f200787496 Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Fri, 3 Aug 2018 16:11:40 -0700 Subject: [PATCH 5/7] Add e2e test for TTL after finished --- test/e2e/apps/job.go | 6 +- test/e2e/auth/metadata_concealment.go | 2 +- test/e2e/common/BUILD | 5 ++ test/e2e/common/ttlafterfinished.go | 101 ++++++++++++++++++++++++++ test/e2e/framework/BUILD | 1 + test/e2e/framework/jobs_util.go | 52 ++++++++++++- 6 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 test/e2e/common/ttlafterfinished.go diff --git a/test/e2e/apps/job.go b/test/e2e/apps/job.go index 29b5afd7f3a..d70713067ee 100644 --- a/test/e2e/apps/job.go +++ b/test/e2e/apps/job.go @@ -44,7 +44,7 @@ var _ = SIGDescribe("Job", func() { Expect(err).NotTo(HaveOccurred()) By("Ensuring job reaches completions") - err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, completions) + err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions) Expect(err).NotTo(HaveOccurred()) }) @@ -63,7 +63,7 @@ var _ = SIGDescribe("Job", func() { Expect(err).NotTo(HaveOccurred()) By("Ensuring job reaches completions") - err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, completions) + err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions) Expect(err).NotTo(HaveOccurred()) }) @@ -84,7 +84,7 @@ var _ = SIGDescribe("Job", func() { Expect(err).NotTo(HaveOccurred()) By("Ensuring job reaches completions") - err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, *job.Spec.Completions) + err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, *job.Spec.Completions) Expect(err).NotTo(HaveOccurred()) }) diff --git a/test/e2e/auth/metadata_concealment.go b/test/e2e/auth/metadata_concealment.go index 01d46bf8dd7..e10d307c841 100644 --- a/test/e2e/auth/metadata_concealment.go +++ b/test/e2e/auth/metadata_concealment.go @@ -58,7 +58,7 @@ var _ = SIGDescribe("Metadata Concealment", func() { Expect(err).NotTo(HaveOccurred()) By("Ensuring job reaches completions") - err = framework.WaitForJobFinish(f.ClientSet, f.Namespace.Name, job.Name, int32(1)) + err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, int32(1)) Expect(err).NotTo(HaveOccurred()) }) }) diff --git a/test/e2e/common/BUILD b/test/e2e/common/BUILD index de57e2556f3..822e24b0d24 100644 --- a/test/e2e/common/BUILD +++ b/test/e2e/common/BUILD @@ -33,6 +33,7 @@ go_library( "secrets.go", "secrets_volume.go", "sysctl.go", + "ttlafterfinished.go", "util.go", "volumes.go", ], @@ -42,13 +43,16 @@ go_library( "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/conditions:go_default_library", + "//pkg/features:go_default_library", "//pkg/kubelet:go_default_library", "//pkg/kubelet/apis:go_default_library", "//pkg/kubelet/images:go_default_library", "//pkg/kubelet/sysctl:go_default_library", "//pkg/security/apparmor:go_default_library", + "//pkg/util/slice:go_default_library", "//pkg/util/version:go_default_library", "//staging/src/k8s.io/api/autoscaling/v1:go_default_library", + "//staging/src/k8s.io/api/batch/v1:go_default_library", "//staging/src/k8s.io/api/coordination/v1beta1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", @@ -64,6 +68,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/scale:go_default_library", "//staging/src/k8s.io/client-go/tools/cache:go_default_library", diff --git a/test/e2e/common/ttlafterfinished.go b/test/e2e/common/ttlafterfinished.go new file mode 100644 index 00000000000..1c62997bd23 --- /dev/null +++ b/test/e2e/common/ttlafterfinished.go @@ -0,0 +1,101 @@ +/* +Copyright 2018 The Kubernetes 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 common + +import ( + "time" + + batch "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/wait" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/util/slice" + "k8s.io/kubernetes/test/e2e/framework" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const dummyFinalizer = "k8s.io/dummy-finalizer" + +var _ = framework.KubeDescribe("TTLAfterFinished", func() { + f := framework.NewDefaultFramework("ttlafterfinished") + + It("Job should be deleted once it finishes after TTL seconds [Feature:TTLAfterFinished]", func() { + if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { + framework.Skipf("Skip because %s feature is not enabled; run test with --feature-gates=%s=true", features.TTLAfterFinished, features.TTLAfterFinished) + } + testFinishedJob(f) + }) +}) + +func cleanupJob(f *framework.Framework, job *batch.Job) { + ns := f.Namespace.Name + c := f.ClientSet + + framework.Logf("Remove the Job's dummy finalizer; the Job should be deleted cascadingly") + removeFinalizerFunc := func(j *batch.Job) { + j.ObjectMeta.Finalizers = slice.RemoveString(j.ObjectMeta.Finalizers, dummyFinalizer, nil) + } + _, err := framework.UpdateJobWithRetries(c, ns, job.Name, removeFinalizerFunc) + Expect(err).NotTo(HaveOccurred()) + framework.WaitForJobGone(c, ns, job.Name, wait.ForeverTestTimeout) + + err = framework.WaitForAllJobPodsGone(c, ns, job.Name) + Expect(err).NotTo(HaveOccurred()) +} + +func testFinishedJob(f *framework.Framework) { + ns := f.Namespace.Name + c := f.ClientSet + + parallelism := int32(1) + completions := int32(1) + backoffLimit := int32(2) + ttl := int32(10) + + job := framework.NewTestJob("randomlySucceedOrFail", "rand-non-local", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit) + job.Spec.TTLSecondsAfterFinished = &ttl + job.ObjectMeta.Finalizers = []string{dummyFinalizer} + defer cleanupJob(f, job) + + framework.Logf("Create a Job %s/%s with TTL", job.Namespace, job.Name) + job, err := framework.CreateJob(c, ns, job) + Expect(err).NotTo(HaveOccurred()) + + framework.Logf("Wait for the Job to finish") + err = framework.WaitForJobFinish(c, ns, job.Name) + Expect(err).NotTo(HaveOccurred()) + + framework.Logf("Wait for TTL after finished controller to delete the Job") + err = framework.WaitForJobDeleting(c, ns, job.Name) + Expect(err).NotTo(HaveOccurred()) + + framework.Logf("Check Job's deletionTimestamp and compare with the time when the Job finished") + job, err = framework.GetJob(c, ns, job.Name) + Expect(err).NotTo(HaveOccurred()) + finishTime := framework.JobFinishTime(job) + finishTimeUTC := finishTime.UTC() + Expect(finishTime.IsZero()).NotTo(BeTrue()) + + deleteAtUTC := job.ObjectMeta.DeletionTimestamp.UTC() + Expect(deleteAtUTC).NotTo(BeNil()) + + expireAtUTC := finishTimeUTC.Add(time.Duration(ttl) * time.Second) + Expect(deleteAtUTC.Before(expireAtUTC)).To(BeFalse()) +} diff --git a/test/e2e/framework/BUILD b/test/e2e/framework/BUILD index a0ebaaf3c2c..e468c6568e0 100644 --- a/test/e2e/framework/BUILD +++ b/test/e2e/framework/BUILD @@ -59,6 +59,7 @@ go_library( "//pkg/cloudprovider/providers/gce:go_default_library", "//pkg/controller:go_default_library", "//pkg/controller/deployment/util:go_default_library", + "//pkg/controller/job:go_default_library", "//pkg/controller/nodelifecycle:go_default_library", "//pkg/controller/service:go_default_library", "//pkg/features:go_default_library", diff --git a/test/e2e/framework/jobs_util.go b/test/e2e/framework/jobs_util.go index c5c97f1c772..e3acdac3661 100644 --- a/test/e2e/framework/jobs_util.go +++ b/test/e2e/framework/jobs_util.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" + jobutil "k8s.io/kubernetes/pkg/controller/job" ) const ( @@ -181,8 +182,8 @@ func WaitForAllJobPodsRunning(c clientset.Interface, ns, jobName string, paralle }) } -// WaitForJobFinish uses c to wait for compeletions to complete for the Job jobName in namespace ns. -func WaitForJobFinish(c clientset.Interface, ns, jobName string, completions int32) error { +// WaitForJobComplete uses c to wait for compeletions to complete for the Job jobName in namespace ns. +func WaitForJobComplete(c clientset.Interface, ns, jobName string, completions int32) error { return wait.Poll(Poll, JobTimeout, func() (bool, error) { curr, err := c.BatchV1().Jobs(ns).Get(jobName, metav1.GetOptions{}) if err != nil { @@ -192,6 +193,17 @@ func WaitForJobFinish(c clientset.Interface, ns, jobName string, completions int }) } +// WaitForJobFinish uses c to wait for the Job jobName in namespace ns to finish (either Failed or Complete). +func WaitForJobFinish(c clientset.Interface, ns, jobName string) error { + return wait.PollImmediate(Poll, JobTimeout, func() (bool, error) { + curr, err := c.BatchV1().Jobs(ns).Get(jobName, metav1.GetOptions{}) + if err != nil { + return false, err + } + return jobutil.IsJobFinished(curr), nil + }) +} + // WaitForJobFailure uses c to wait for up to timeout for the Job named jobName in namespace ns to fail. func WaitForJobFailure(c clientset.Interface, ns, jobName string, timeout time.Duration, reason string) error { return wait.Poll(Poll, timeout, func() (bool, error) { @@ -239,6 +251,18 @@ func CheckForAllJobPodsRunning(c clientset.Interface, ns, jobName string, parall return count == parallelism, nil } +// WaitForAllJobPodsRunning wait for all pods for the Job named jobName in namespace ns +// to be deleted. +func WaitForAllJobPodsGone(c clientset.Interface, ns, jobName string) error { + return wait.PollImmediate(Poll, JobTimeout, func() (bool, error) { + pods, err := GetJobPods(c, ns, jobName) + if err != nil { + return false, err + } + return len(pods.Items) == 0, nil + }) +} + func newBool(val bool) *bool { p := new(bool) *p = val @@ -250,7 +274,7 @@ type updateJobFunc func(*batch.Job) func UpdateJobWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateJobFunc) (job *batch.Job, err error) { jobs := c.BatchV1().Jobs(namespace) var updateErr error - pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) { + pollErr := wait.PollImmediate(Poll, JobTimeout, func() (bool, error) { if job, err = jobs.Get(name, metav1.GetOptions{}); err != nil { return false, err } @@ -268,3 +292,25 @@ func UpdateJobWithRetries(c clientset.Interface, namespace, name string, applyUp } return job, pollErr } + +// WaitForJobDeleting uses c to wait for the Job jobName in namespace ns to have +// a non-nil deletionTimestamp (i.e. being deleted). +func WaitForJobDeleting(c clientset.Interface, ns, jobName string) error { + return wait.PollImmediate(Poll, JobTimeout, func() (bool, error) { + curr, err := c.BatchV1().Jobs(ns).Get(jobName, metav1.GetOptions{}) + if err != nil { + return false, err + } + return curr.ObjectMeta.DeletionTimestamp != nil, nil + }) +} + +func JobFinishTime(finishedJob *batch.Job) metav1.Time { + var finishTime metav1.Time + for _, c := range finishedJob.Status.Conditions { + if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue { + return c.LastTransitionTime + } + } + return finishTime +} From cbdc9b671f33b0f0679e790cc462b25d1476a3af Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Tue, 14 Aug 2018 15:31:32 -0700 Subject: [PATCH 6/7] Make number of workers configurable --- api/api-rules/violation_exceptions.list | 2 + .../app/controllermanager.go | 2 +- cmd/kube-controller-manager/app/core.go | 2 +- cmd/kube-controller-manager/app/options/BUILD | 1 + .../app/options/options.go | 9 +++ .../app/options/options_test.go | 4 ++ .../app/options/ttlafterfinishedcontroller.go | 58 +++++++++++++++++++ pkg/controller/apis/config/types.go | 10 ++++ .../apis/config/v1alpha1/defaults.go | 3 + .../config/v1alpha1/types.go | 10 ++++ 10 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 cmd/kube-controller-manager/app/options/ttlafterfinishedcontroller.go diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index df8bfded969..8add67dc09a 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -126,6 +126,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,K API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ResourceQuotaController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,SAController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,ServiceController +API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,KubeControllerManagerConfiguration,TTLAfterFinishedController API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,NamespaceSyncPeriod API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NamespaceControllerConfiguration,ConcurrentNamespaceSyncs API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,NodeIPAMControllerConfiguration,ServiceCIDR @@ -156,6 +157,7 @@ API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,S API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,ConcurrentSATokenSyncs API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,SAControllerConfiguration,RootCAFile API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,ServiceControllerConfiguration,ConcurrentServiceSyncs +API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,TTLAfterFinishedControllerConfiguration,ConcurrentTTLSyncs API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableHostPathProvisioning API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,EnableDynamicProvisioning API rule violation: names_match,k8s.io/kube-controller-manager/config/v1alpha1,VolumeConfiguration,PersistentVolumeRecyclerConfiguration diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 1d0a19e759d..0f33ec168c0 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -378,7 +378,7 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController controllers["pvc-protection"] = startPVCProtectionController controllers["pv-protection"] = startPVProtectionController - controllers["ttl-after-finished-controller"] = startTTLAfterFinishedController + controllers["ttl-after-finished"] = startTTLAfterFinishedController return controllers } diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index 202a7f9668b..f6a273385a8 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -426,6 +426,6 @@ func startTTLAfterFinishedController(ctx ControllerContext) (http.Handler, bool, go ttlafterfinished.New( ctx.InformerFactory.Batch().V1().Jobs(), ctx.ClientBuilder.ClientOrDie("ttl-after-finished-controller"), - ).Run(5, ctx.Stop) + ).Run(int(ctx.ComponentConfig.TTLAfterFinishedController.ConcurrentTTLSyncs), ctx.Stop) return nil, true, nil } diff --git a/cmd/kube-controller-manager/app/options/BUILD b/cmd/kube-controller-manager/app/options/BUILD index 23d29b8bc0e..fcae9358cf7 100644 --- a/cmd/kube-controller-manager/app/options/BUILD +++ b/cmd/kube-controller-manager/app/options/BUILD @@ -28,6 +28,7 @@ go_library( "replicationcontroller.go", "resourcequotacontroller.go", "serviceaccountcontroller.go", + "ttlafterfinishedcontroller.go", ], importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app/options", deps = [ diff --git a/cmd/kube-controller-manager/app/options/options.go b/cmd/kube-controller-manager/app/options/options.go index 56178e9ed5a..22cf24a12e3 100644 --- a/cmd/kube-controller-manager/app/options/options.go +++ b/cmd/kube-controller-manager/app/options/options.go @@ -77,6 +77,7 @@ type KubeControllerManagerOptions struct { ReplicationController *ReplicationControllerOptions ResourceQuotaController *ResourceQuotaControllerOptions SAController *SAControllerOptions + TTLAfterFinishedController *TTLAfterFinishedControllerOptions SecureServing *apiserveroptions.SecureServingOptionsWithLoopback // TODO: remove insecure serving mode @@ -172,6 +173,9 @@ func NewKubeControllerManagerOptions() (*KubeControllerManagerOptions, error) { ServiceController: &cmoptions.ServiceControllerOptions{ ConcurrentServiceSyncs: componentConfig.ServiceController.ConcurrentServiceSyncs, }, + TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{ + ConcurrentTTLSyncs: componentConfig.TTLAfterFinishedController.ConcurrentTTLSyncs, + }, SecureServing: apiserveroptions.NewSecureServingOptions().WithLoopback(), InsecureServing: (&apiserveroptions.DeprecatedInsecureServingOptions{ BindAddress: net.ParseIP(componentConfig.Generic.Address), @@ -251,6 +255,7 @@ func (s *KubeControllerManagerOptions) Flags(allControllers []string, disabledBy s.ReplicationController.AddFlags(fss.FlagSet("replicationcontroller")) s.ResourceQuotaController.AddFlags(fss.FlagSet("resourcequota controller")) s.SAController.AddFlags(fss.FlagSet("serviceaccount controller")) + s.TTLAfterFinishedController.AddFlags(fss.FlagSet("ttl-after-finished controller")) fs := fss.FlagSet("misc") fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).") @@ -328,6 +333,9 @@ func (s *KubeControllerManagerOptions) ApplyTo(c *kubecontrollerconfig.Config) e if err := s.ServiceController.ApplyTo(&c.ComponentConfig.ServiceController); err != nil { return err } + if err := s.TTLAfterFinishedController.ApplyTo(&c.ComponentConfig.TTLAfterFinishedController); err != nil { + return err + } if err := s.InsecureServing.ApplyTo(&c.InsecureServing, &c.LoopbackClientConfig); err != nil { return err } @@ -376,6 +384,7 @@ func (s *KubeControllerManagerOptions) Validate(allControllers []string, disable errs = append(errs, s.ResourceQuotaController.Validate()...) errs = append(errs, s.SAController.Validate()...) errs = append(errs, s.ServiceController.Validate()...) + errs = append(errs, s.TTLAfterFinishedController.Validate()...) errs = append(errs, s.SecureServing.Validate()...) errs = append(errs, s.InsecureServing.Validate()...) errs = append(errs, s.Authentication.Validate()...) diff --git a/cmd/kube-controller-manager/app/options/options_test.go b/cmd/kube-controller-manager/app/options/options_test.go index 5a59471ac44..4129038be7b 100644 --- a/cmd/kube-controller-manager/app/options/options_test.go +++ b/cmd/kube-controller-manager/app/options/options_test.go @@ -116,6 +116,7 @@ func TestAddFlags(t *testing.T) { "--cert-dir=/a/b/c", "--bind-address=192.168.4.21", "--secure-port=10001", + "--concurrent-ttl-after-finished-syncs=8", } fs.Parse(args) // Sort GCIgnoredResources because it's built from a map, which means the @@ -255,6 +256,9 @@ func TestAddFlags(t *testing.T) { ServiceController: &cmoptions.ServiceControllerOptions{ ConcurrentServiceSyncs: 2, }, + TTLAfterFinishedController: &TTLAfterFinishedControllerOptions{ + ConcurrentTTLSyncs: 8, + }, SecureServing: (&apiserveroptions.SecureServingOptions{ BindPort: 10001, BindAddress: net.ParseIP("192.168.4.21"), diff --git a/cmd/kube-controller-manager/app/options/ttlafterfinishedcontroller.go b/cmd/kube-controller-manager/app/options/ttlafterfinishedcontroller.go new file mode 100644 index 00000000000..2f7fe765eac --- /dev/null +++ b/cmd/kube-controller-manager/app/options/ttlafterfinishedcontroller.go @@ -0,0 +1,58 @@ +/* +Copyright 2018 The Kubernetes 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 options + +import ( + "github.com/spf13/pflag" + + kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config" +) + +// TTLAfterFinishedControllerOptions holds the TTLAfterFinishedController options. +type TTLAfterFinishedControllerOptions struct { + ConcurrentTTLSyncs int32 +} + +// AddFlags adds flags related to TTLAfterFinishedController for controller manager to the specified FlagSet. +func (o *TTLAfterFinishedControllerOptions) AddFlags(fs *pflag.FlagSet) { + if o == nil { + return + } + + fs.Int32Var(&o.ConcurrentTTLSyncs, "concurrent-ttl-after-finished-syncs", o.ConcurrentTTLSyncs, "The number of TTL-after-finished controller workers that are allowed to sync concurrently.") +} + +// ApplyTo fills up TTLAfterFinishedController config with options. +func (o *TTLAfterFinishedControllerOptions) ApplyTo(cfg *kubectrlmgrconfig.TTLAfterFinishedControllerConfiguration) error { + if o == nil { + return nil + } + + cfg.ConcurrentTTLSyncs = o.ConcurrentTTLSyncs + + return nil +} + +// Validate checks validation of TTLAfterFinishedControllerOptions. +func (o *TTLAfterFinishedControllerOptions) Validate() []error { + if o == nil { + return nil + } + + errs := []error{} + return errs +} diff --git a/pkg/controller/apis/config/types.go b/pkg/controller/apis/config/types.go index b7abe6b9234..c04a1b3552d 100644 --- a/pkg/controller/apis/config/types.go +++ b/pkg/controller/apis/config/types.go @@ -96,6 +96,9 @@ type KubeControllerManagerConfiguration struct { // ServiceControllerConfiguration holds configuration for ServiceController // related features. ServiceController ServiceControllerConfiguration + // TTLAfterFinishedControllerConfiguration holds configuration for + // TTLAfterFinishedController related features. + TTLAfterFinishedController TTLAfterFinishedControllerConfiguration } // GenericControllerManagerConfiguration holds configuration for a generic controller-manager @@ -438,3 +441,10 @@ type PersistentVolumeRecyclerConfiguration struct { // in a multi-node cluster. IncrementTimeoutHostPath int32 } + +// TTLAfterFinishedControllerConfiguration contains elements describing TTLAfterFinishedController. +type TTLAfterFinishedControllerConfiguration struct { + // concurrentTTLSyncs is the number of TTL-after-finished collector workers that are + // allowed to sync concurrently. + ConcurrentTTLSyncs int32 +} diff --git a/pkg/controller/apis/config/v1alpha1/defaults.go b/pkg/controller/apis/config/v1alpha1/defaults.go index 6e51aa964d2..af965725660 100644 --- a/pkg/controller/apis/config/v1alpha1/defaults.go +++ b/pkg/controller/apis/config/v1alpha1/defaults.go @@ -48,6 +48,9 @@ func SetDefaults_KubeControllerManagerConfiguration(obj *kubectrlmgrconfigv1alph if obj.SAController.ConcurrentSATokenSyncs == 0 { obj.SAController.ConcurrentSATokenSyncs = 5 } + if obj.TTLAfterFinishedController.ConcurrentTTLSyncs <= 0 { + obj.TTLAfterFinishedController.ConcurrentTTLSyncs = 5 + } // These defaults override the recommended defaults from the apimachineryconfigv1alpha1 package that are applied automatically // These client-connection defaults are specific to the kube-controller-manager diff --git a/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/types.go b/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/types.go index 5bcfadea6f5..2e9f45b5b49 100644 --- a/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/types.go +++ b/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/types.go @@ -145,6 +145,9 @@ type KubeControllerManagerConfiguration struct { // ServiceControllerConfiguration holds configuration for ServiceController // related features. ServiceController ServiceControllerConfiguration + // TTLAfterFinishedControllerConfiguration holds configuration for + // TTLAfterFinishedController related features. + TTLAfterFinishedController TTLAfterFinishedControllerConfiguration } // GenericControllerManagerConfiguration holds configuration for a generic controller-manager. @@ -438,3 +441,10 @@ type ServiceControllerConfiguration struct { // management, but more CPU (and network) load. ConcurrentServiceSyncs int32 } + +// TTLAfterFinishedControllerConfiguration contains elements describing TTLAfterFinishedController. +type TTLAfterFinishedControllerConfiguration struct { + // concurrentTTLSyncs is the number of TTL-after-finished collector workers that are + // allowed to sync concurrently. + ConcurrentTTLSyncs int32 +} From 13b76d5fb4eb680a78eb9760f8bf0b2dd4d75d9c Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Tue, 4 Sep 2018 11:24:24 -0700 Subject: [PATCH 7/7] Autogen make clean && make generated_files --- .../v1alpha1/zz_generated.conversion.go | 36 +++++++++++++++++++ .../apis/config/zz_generated.deepcopy.go | 17 +++++++++ .../config/v1alpha1/zz_generated.deepcopy.go | 17 +++++++++ 3 files changed, 70 insertions(+) diff --git a/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go index 70b6319bd09..1e7718d084c 100644 --- a/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go +++ b/pkg/controller/apis/config/v1alpha1/zz_generated.conversion.go @@ -289,6 +289,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*v1alpha1.TTLAfterFinishedControllerConfiguration)(nil), (*config.TTLAfterFinishedControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(a.(*v1alpha1.TTLAfterFinishedControllerConfiguration), b.(*config.TTLAfterFinishedControllerConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*config.TTLAfterFinishedControllerConfiguration)(nil), (*v1alpha1.TTLAfterFinishedControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(a.(*config.TTLAfterFinishedControllerConfiguration), b.(*v1alpha1.TTLAfterFinishedControllerConfiguration), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*v1alpha1.VolumeConfiguration)(nil), (*config.VolumeConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_VolumeConfiguration_To_config_VolumeConfiguration(a.(*v1alpha1.VolumeConfiguration), b.(*config.VolumeConfiguration), scope) }); err != nil { @@ -734,6 +744,9 @@ func autoConvert_v1alpha1_KubeControllerManagerConfiguration_To_config_KubeContr if err := Convert_v1alpha1_ServiceControllerConfiguration_To_config_ServiceControllerConfiguration(&in.ServiceController, &out.ServiceController, s); err != nil { return err } + if err := Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil { + return err + } return nil } @@ -806,6 +819,9 @@ func autoConvert_config_KubeControllerManagerConfiguration_To_v1alpha1_KubeContr if err := Convert_config_ServiceControllerConfiguration_To_v1alpha1_ServiceControllerConfiguration(&in.ServiceController, &out.ServiceController, s); err != nil { return err } + if err := Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(&in.TTLAfterFinishedController, &out.TTLAfterFinishedController, s); err != nil { + return err + } return nil } @@ -1070,6 +1086,26 @@ func autoConvert_config_ServiceControllerConfiguration_To_v1alpha1_ServiceContro return nil } +func autoConvert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(in *v1alpha1.TTLAfterFinishedControllerConfiguration, out *config.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error { + out.ConcurrentTTLSyncs = in.ConcurrentTTLSyncs + return nil +} + +// Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(in *v1alpha1.TTLAfterFinishedControllerConfiguration, out *config.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_TTLAfterFinishedControllerConfiguration_To_config_TTLAfterFinishedControllerConfiguration(in, out, s) +} + +func autoConvert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(in *config.TTLAfterFinishedControllerConfiguration, out *v1alpha1.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error { + out.ConcurrentTTLSyncs = in.ConcurrentTTLSyncs + return nil +} + +// Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration is an autogenerated conversion function. +func Convert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(in *config.TTLAfterFinishedControllerConfiguration, out *v1alpha1.TTLAfterFinishedControllerConfiguration, s conversion.Scope) error { + return autoConvert_config_TTLAfterFinishedControllerConfiguration_To_v1alpha1_TTLAfterFinishedControllerConfiguration(in, out, s) +} + func autoConvert_v1alpha1_VolumeConfiguration_To_config_VolumeConfiguration(in *v1alpha1.VolumeConfiguration, out *config.VolumeConfiguration, s conversion.Scope) error { if err := v1.Convert_Pointer_bool_To_bool(&in.EnableHostPathProvisioning, &out.EnableHostPathProvisioning, s); err != nil { return err diff --git a/pkg/controller/apis/config/zz_generated.deepcopy.go b/pkg/controller/apis/config/zz_generated.deepcopy.go index a859a3ef830..caebbe8789a 100644 --- a/pkg/controller/apis/config/zz_generated.deepcopy.go +++ b/pkg/controller/apis/config/zz_generated.deepcopy.go @@ -285,6 +285,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa out.ResourceQuotaController = in.ResourceQuotaController out.SAController = in.SAController out.ServiceController = in.ServiceController + out.TTLAfterFinishedController = in.TTLAfterFinishedController return } @@ -489,6 +490,22 @@ func (in *ServiceControllerConfiguration) DeepCopy() *ServiceControllerConfigura return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TTLAfterFinishedControllerConfiguration) DeepCopyInto(out *TTLAfterFinishedControllerConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TTLAfterFinishedControllerConfiguration. +func (in *TTLAfterFinishedControllerConfiguration) DeepCopy() *TTLAfterFinishedControllerConfiguration { + if in == nil { + return nil + } + out := new(TTLAfterFinishedControllerConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) { *out = *in diff --git a/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/zz_generated.deepcopy.go index b975a124ac5..f8d242772ce 100644 --- a/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/kube-controller-manager/config/v1alpha1/zz_generated.deepcopy.go @@ -300,6 +300,7 @@ func (in *KubeControllerManagerConfiguration) DeepCopyInto(out *KubeControllerMa out.ResourceQuotaController = in.ResourceQuotaController out.SAController = in.SAController out.ServiceController = in.ServiceController + out.TTLAfterFinishedController = in.TTLAfterFinishedController return } @@ -509,6 +510,22 @@ func (in *ServiceControllerConfiguration) DeepCopy() *ServiceControllerConfigura return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TTLAfterFinishedControllerConfiguration) DeepCopyInto(out *TTLAfterFinishedControllerConfiguration) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TTLAfterFinishedControllerConfiguration. +func (in *TTLAfterFinishedControllerConfiguration) DeepCopy() *TTLAfterFinishedControllerConfiguration { + if in == nil { + return nil + } + out := new(TTLAfterFinishedControllerConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolumeConfiguration) DeepCopyInto(out *VolumeConfiguration) { *out = *in