diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index b499dccbc16..7450953e313 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -40985,6 +40985,11 @@ "description": "ConcurrencyPolicy specifies how to treat concurrent executions of a Job.", "type": "string" }, + "failedJobsHistoryLimit": { + "description": "The number of failed finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", + "type": "integer", + "format": "int32" + }, "jobTemplate": { "description": "JobTemplate is the object that describes the job that will be created when executing a CronJob.", "$ref": "#/definitions/io.k8s.kubernetes.pkg.apis.batch.v2alpha1.JobTemplateSpec" @@ -40998,6 +41003,11 @@ "type": "integer", "format": "int64" }, + "successfulJobsHistoryLimit": { + "description": "The number of successful finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", + "type": "integer", + "format": "int32" + }, "suspend": { "description": "Suspend flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "type": "boolean" diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index 415a5045e2a..8ad142d8709 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -542,6 +542,14 @@ func batchFuncs(t apitesting.TestingCommon) []interface{} { sds := int64(c.RandUint64()) sj.StartingDeadlineSeconds = &sds sj.Schedule = c.RandString() + if hasSuccessLimit := c.RandBool(); hasSuccessLimit { + successfulJobsHistoryLimit := int32(c.Rand.Int31()) + sj.SuccessfulJobsHistoryLimit = &successfulJobsHistoryLimit + } + if hasFailedLimit := c.RandBool(); hasFailedLimit { + failedJobsHistoryLimit := int32(c.Rand.Int31()) + sj.FailedJobsHistoryLimit = &failedJobsHistoryLimit + } }, func(cp *batch.ConcurrencyPolicy, c fuzz.Continue) { policies := []batch.ConcurrencyPolicy{batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent} diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go index ef0037e0d00..2113dca18a6 100644 --- a/pkg/apis/batch/types.go +++ b/pkg/apis/batch/types.go @@ -244,6 +244,16 @@ type CronJobSpec struct { // JobTemplate is the object that describes the job that will be created when // executing a CronJob. JobTemplate JobTemplateSpec + + // The number of successful finished jobs to retain. + // This is a pointer to distinguish between explicit zero and not specified. + // +optional + SuccessfulJobsHistoryLimit *int32 + + // The number of failed finished jobs to retain. + // This is a pointer to distinguish between explicit zero and not specified. + // +optional + FailedJobsHistoryLimit *int32 } // ConcurrencyPolicy describes how the job will be handled. diff --git a/pkg/apis/batch/v2alpha1/generated.pb.go b/pkg/apis/batch/v2alpha1/generated.pb.go index bb7b1046ffb..b722f3dc935 100644 --- a/pkg/apis/batch/v2alpha1/generated.pb.go +++ b/pkg/apis/batch/v2alpha1/generated.pb.go @@ -244,6 +244,16 @@ func (m *CronJobSpec) MarshalTo(data []byte) (int, error) { return 0, err } i += n5 + if m.SuccessfulJobsHistoryLimit != nil { + data[i] = 0x30 + i++ + i = encodeVarintGenerated(data, i, uint64(*m.SuccessfulJobsHistoryLimit)) + } + if m.FailedJobsHistoryLimit != nil { + data[i] = 0x38 + i++ + i = encodeVarintGenerated(data, i, uint64(*m.FailedJobsHistoryLimit)) + } return i, nil } @@ -673,6 +683,12 @@ func (m *CronJobSpec) Size() (n int) { } l = m.JobTemplate.Size() n += 1 + l + sovGenerated(uint64(l)) + if m.SuccessfulJobsHistoryLimit != nil { + n += 1 + sovGenerated(uint64(*m.SuccessfulJobsHistoryLimit)) + } + if m.FailedJobsHistoryLimit != nil { + n += 1 + sovGenerated(uint64(*m.FailedJobsHistoryLimit)) + } return n } @@ -849,6 +865,8 @@ func (this *CronJobSpec) String() string { `ConcurrencyPolicy:` + fmt.Sprintf("%v", this.ConcurrencyPolicy) + `,`, `Suspend:` + valueToStringGenerated(this.Suspend) + `,`, `JobTemplate:` + strings.Replace(strings.Replace(this.JobTemplate.String(), "JobTemplateSpec", "JobTemplateSpec", 1), `&`, ``, 1) + `,`, + `SuccessfulJobsHistoryLimit:` + valueToStringGenerated(this.SuccessfulJobsHistoryLimit) + `,`, + `FailedJobsHistoryLimit:` + valueToStringGenerated(this.FailedJobsHistoryLimit) + `,`, `}`, }, "") return s @@ -1371,6 +1389,46 @@ func (m *CronJobSpec) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SuccessfulJobsHistoryLimit", 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.SuccessfulJobsHistoryLimit = &v + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FailedJobsHistoryLimit", 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.FailedJobsHistoryLimit = &v default: iNdEx = preIndex skippy, err := skipGenerated(data[iNdEx:]) @@ -2707,78 +2765,82 @@ var ( ) var fileDescriptorGenerated = []byte{ - // 1162 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4b, 0x6f, 0x23, 0x45, - 0x10, 0xce, 0xd8, 0x89, 0x1f, 0xed, 0xcd, 0xab, 0x21, 0x5a, 0x13, 0x24, 0x3b, 0xb2, 0x04, 0xca, - 0xae, 0x76, 0x67, 0x88, 0x37, 0x5a, 0x96, 0x3d, 0x20, 0xed, 0x04, 0x21, 0x11, 0x25, 0xda, 0xa8, - 0x9d, 0x65, 0x11, 0x04, 0x69, 0xdb, 0xe3, 0x8a, 0x3d, 0x9b, 0x79, 0x31, 0xdd, 0xb6, 0xc8, 0x8d, - 0x33, 0x27, 0xee, 0xfc, 0x00, 0xfe, 0x02, 0x42, 0x1c, 0x39, 0x84, 0x5b, 0x0e, 0x1c, 0xe0, 0x62, - 0x91, 0xe1, 0x5f, 0xe4, 0x84, 0xa6, 0xdd, 0xf3, 0xf0, 0x2b, 0x1b, 0x07, 0x29, 0x12, 0xb7, 0xe9, - 0xea, 0xfa, 0xbe, 0xae, 0xae, 0xfa, 0xba, 0x6a, 0xd0, 0x47, 0x27, 0x4f, 0x98, 0x6a, 0xba, 0xda, - 0x49, 0xb7, 0x09, 0xbe, 0x03, 0x1c, 0x98, 0xe6, 0x9d, 0xb4, 0x35, 0xea, 0x99, 0x4c, 0x6b, 0x52, - 0x6e, 0x74, 0xb4, 0x5e, 0x9d, 0x5a, 0x5e, 0x87, 0x6e, 0x69, 0x6d, 0x70, 0xc0, 0xa7, 0x1c, 0x5a, - 0xaa, 0xe7, 0xbb, 0xdc, 0xc5, 0xf7, 0x06, 0x50, 0x35, 0x81, 0xaa, 0xde, 0x49, 0x5b, 0x0d, 0xa1, - 0xaa, 0x80, 0xaa, 0x11, 0x74, 0xfd, 0x61, 0xdb, 0xe4, 0x9d, 0x6e, 0x53, 0x35, 0x5c, 0x5b, 0x6b, - 0xbb, 0x6d, 0x57, 0x13, 0x0c, 0xcd, 0xee, 0xb1, 0x58, 0x89, 0x85, 0xf8, 0x1a, 0x30, 0xaf, 0x6f, - 0xcb, 0xa0, 0xa8, 0x67, 0xda, 0xd4, 0xe8, 0x98, 0x0e, 0xf8, 0xa7, 0x49, 0x58, 0x36, 0x70, 0xaa, - 0xf5, 0xc6, 0xe2, 0x59, 0xd7, 0xa6, 0xa1, 0xfc, 0xae, 0xc3, 0x4d, 0x1b, 0xc6, 0x00, 0x8f, 0xdf, - 0x04, 0x60, 0x46, 0x07, 0x6c, 0x3a, 0x86, 0x7b, 0x34, 0x0d, 0xd7, 0xe5, 0xa6, 0xa5, 0x99, 0x0e, - 0x67, 0xdc, 0x1f, 0x03, 0xa5, 0xee, 0xc4, 0xc0, 0xef, 0x81, 0x9f, 0x5c, 0x08, 0xbe, 0xa5, 0xb6, - 0x67, 0xc1, 0xa4, 0x3b, 0x3d, 0x98, 0x5a, 0x9e, 0x09, 0xde, 0xb5, 0x9f, 0x32, 0x28, 0xbf, 0xe3, - 0xbb, 0xce, 0xae, 0xdb, 0xc4, 0xaf, 0x50, 0x21, 0x4c, 0x54, 0x8b, 0x72, 0x5a, 0x56, 0x36, 0x94, - 0xcd, 0x52, 0xfd, 0x03, 0x55, 0x16, 0x2c, 0x1d, 0x77, 0x52, 0xb2, 0xd0, 0x5b, 0xed, 0x6d, 0xa9, - 0xcf, 0x9b, 0xaf, 0xc1, 0xe0, 0xfb, 0xc0, 0xa9, 0x8e, 0xcf, 0xfa, 0xd5, 0xb9, 0xa0, 0x5f, 0x45, - 0x89, 0x8d, 0xc4, 0xac, 0xf8, 0x0b, 0x34, 0xcf, 0x3c, 0x30, 0xca, 0x19, 0xc1, 0xfe, 0x58, 0xbd, - 0xb6, 0x1c, 0x54, 0x19, 0x63, 0xc3, 0x03, 0x43, 0xbf, 0x23, 0xcf, 0x98, 0x0f, 0x57, 0x44, 0x30, - 0xe2, 0x57, 0x28, 0xc7, 0x38, 0xe5, 0x5d, 0x56, 0xce, 0x0a, 0xee, 0x27, 0x37, 0xe0, 0x16, 0x78, - 0x7d, 0x49, 0xb2, 0xe7, 0x06, 0x6b, 0x22, 0x79, 0x6b, 0xbf, 0x29, 0xa8, 0x24, 0x3d, 0xf7, 0x4c, - 0xc6, 0xf1, 0xd1, 0x58, 0xb6, 0xd4, 0xeb, 0x65, 0x2b, 0x44, 0x8b, 0x5c, 0xad, 0xc8, 0x93, 0x0a, - 0x91, 0x25, 0x95, 0xa9, 0x97, 0x68, 0xc1, 0xe4, 0x60, 0xb3, 0x72, 0x66, 0x23, 0xbb, 0x59, 0xaa, - 0xd7, 0x67, 0xbf, 0x8e, 0xbe, 0x28, 0xe9, 0x17, 0x3e, 0x0b, 0x89, 0xc8, 0x80, 0xaf, 0xf6, 0x7d, - 0x36, 0xbe, 0x46, 0x98, 0x3e, 0xfc, 0x00, 0x15, 0x42, 0xcd, 0xb6, 0xba, 0x16, 0x88, 0x6b, 0x14, - 0x93, 0xb0, 0x1a, 0xd2, 0x4e, 0x62, 0x0f, 0xfc, 0x02, 0xdd, 0x65, 0x9c, 0xfa, 0xdc, 0x74, 0xda, - 0x9f, 0x00, 0x6d, 0x59, 0xa6, 0x03, 0x0d, 0x30, 0x5c, 0xa7, 0xc5, 0x44, 0x4d, 0xb3, 0xfa, 0xbb, - 0x41, 0xbf, 0x7a, 0xb7, 0x31, 0xd9, 0x85, 0x4c, 0xc3, 0xe2, 0x23, 0xb4, 0x6a, 0xb8, 0x8e, 0xd1, - 0xf5, 0x7d, 0x70, 0x8c, 0xd3, 0x03, 0xd7, 0x32, 0x8d, 0x53, 0x51, 0xc8, 0xa2, 0xae, 0xca, 0x68, - 0x56, 0x77, 0x46, 0x1d, 0x2e, 0x27, 0x19, 0xc9, 0x38, 0x11, 0x7e, 0x0f, 0xe5, 0x59, 0x97, 0x79, - 0xe0, 0xb4, 0xca, 0xf3, 0x1b, 0xca, 0x66, 0x41, 0x2f, 0x05, 0xfd, 0x6a, 0xbe, 0x31, 0x30, 0x91, - 0x68, 0x0f, 0x7f, 0x83, 0x4a, 0xaf, 0xdd, 0xe6, 0x21, 0xd8, 0x9e, 0x45, 0x39, 0x94, 0x17, 0x44, - 0x4d, 0x9f, 0xce, 0x90, 0xf8, 0xdd, 0x04, 0x2d, 0x74, 0xfa, 0x96, 0x0c, 0xbd, 0x94, 0xda, 0x20, - 0xe9, 0x33, 0x6a, 0x7f, 0x28, 0x68, 0x71, 0x48, 0x7d, 0xf8, 0x05, 0xca, 0x51, 0x83, 0x9b, 0xbd, - 0xb0, 0x18, 0x61, 0xe1, 0x1f, 0x4e, 0x3f, 0x3f, 0x79, 0x79, 0x04, 0x8e, 0x21, 0xbc, 0x30, 0x24, - 0xe2, 0x7d, 0x26, 0x48, 0x88, 0x24, 0xc3, 0x16, 0x5a, 0xb1, 0x28, 0xe3, 0x51, 0x45, 0x0f, 0x4d, - 0x1b, 0x44, 0x2e, 0x4a, 0xf5, 0xfb, 0xd7, 0x13, 0x6d, 0x88, 0xd0, 0xdf, 0x0e, 0xfa, 0xd5, 0x95, - 0xbd, 0x11, 0x1e, 0x32, 0xc6, 0x5c, 0xfb, 0x31, 0x83, 0xb2, 0xb7, 0xd3, 0x50, 0x0e, 0x87, 0x1a, - 0x4a, 0x7d, 0xb6, 0x62, 0x4d, 0x6d, 0x26, 0x47, 0x23, 0xcd, 0x64, 0x7b, 0x46, 0xde, 0xab, 0x1b, - 0xc9, 0x79, 0x16, 0xdd, 0xd9, 0x75, 0x9b, 0x3b, 0xae, 0xd3, 0x32, 0xb9, 0xe9, 0x3a, 0x78, 0x1b, - 0xcd, 0xf3, 0x53, 0x2f, 0x7a, 0x7e, 0x1b, 0x51, 0x40, 0x87, 0xa7, 0x1e, 0x5c, 0xf6, 0xab, 0x2b, - 0x69, 0xdf, 0xd0, 0x46, 0x84, 0x37, 0xfe, 0x3c, 0x0e, 0x32, 0x23, 0x70, 0x1f, 0x0f, 0x1f, 0x77, - 0xd9, 0xaf, 0x5e, 0x39, 0x09, 0xd4, 0x98, 0x73, 0x38, 0x3c, 0xdc, 0x46, 0x8b, 0x61, 0x41, 0x0f, - 0x7c, 0xb7, 0x39, 0xd0, 0x49, 0x76, 0x66, 0x9d, 0xac, 0xc9, 0x50, 0x16, 0xf7, 0xd2, 0x44, 0x64, - 0x98, 0x17, 0xf7, 0x10, 0x0e, 0x0d, 0x87, 0x3e, 0x75, 0xd8, 0xe0, 0x72, 0x37, 0x53, 0xe5, 0xba, - 0x3c, 0x0d, 0xef, 0x8d, 0xb1, 0x91, 0x09, 0x27, 0xe0, 0xf7, 0x51, 0xce, 0x07, 0xca, 0x5c, 0x47, - 0x3c, 0xf1, 0x62, 0x52, 0x27, 0x22, 0xac, 0x44, 0xee, 0xe2, 0x7b, 0x28, 0x6f, 0x03, 0x63, 0xb4, - 0x0d, 0xe5, 0x9c, 0x70, 0x5c, 0x96, 0x8e, 0xf9, 0xfd, 0x81, 0x99, 0x44, 0xfb, 0xb5, 0x5f, 0x15, - 0x94, 0xbf, 0x9d, 0xb9, 0xd0, 0x18, 0x9e, 0x0b, 0xea, 0x6c, 0xca, 0x9c, 0x32, 0x13, 0x7e, 0xce, - 0x8a, 0xf0, 0xc5, 0x3c, 0xd8, 0x42, 0x25, 0x8f, 0xfa, 0xd4, 0xb2, 0xc0, 0x32, 0x99, 0x2d, 0x6e, - 0xb0, 0xa0, 0x2f, 0x87, 0x5d, 0xec, 0x20, 0x31, 0x93, 0xb4, 0x4f, 0x08, 0x31, 0xdc, 0xf0, 0x77, - 0x24, 0x4c, 0xf1, 0x40, 0x8e, 0x12, 0xb2, 0x93, 0x98, 0x49, 0xda, 0x07, 0x3f, 0x47, 0x6b, 0x83, - 0xce, 0x34, 0x3a, 0x45, 0xb2, 0x62, 0x8a, 0xbc, 0x13, 0xf4, 0xab, 0x6b, 0xcf, 0x26, 0x39, 0x90, - 0xc9, 0x38, 0xfc, 0x35, 0x2a, 0x30, 0xb0, 0xc0, 0xe0, 0xae, 0x2f, 0x25, 0xf4, 0xe8, 0x9a, 0x59, - 0xa7, 0x4d, 0xb0, 0x1a, 0x12, 0xaa, 0xdf, 0x11, 0x73, 0x4f, 0xae, 0x48, 0x4c, 0x89, 0x9f, 0xa2, - 0x25, 0x9b, 0x3a, 0x5d, 0x1a, 0x7b, 0x0a, 0xed, 0x14, 0x74, 0x1c, 0xf4, 0xab, 0x4b, 0xfb, 0x43, - 0x3b, 0x64, 0xc4, 0x13, 0x7f, 0x85, 0x0a, 0x3c, 0x1a, 0x2a, 0x39, 0x11, 0xda, 0x1b, 0x9a, 0xfa, - 0x81, 0xdb, 0x1a, 0x9a, 0x23, 0xb1, 0x1e, 0xe2, 0x21, 0x12, 0x13, 0xd6, 0x7e, 0xc9, 0xa2, 0x62, - 0x32, 0x3d, 0x4e, 0x10, 0x32, 0xa2, 0x67, 0xcd, 0xe4, 0x04, 0xf9, 0x70, 0x36, 0x89, 0xc4, 0x6d, - 0x21, 0xe9, 0xbc, 0xb1, 0x89, 0x91, 0x14, 0x3d, 0x7e, 0x89, 0x8a, 0x62, 0x9e, 0x8b, 0x67, 0x9b, - 0x99, 0xf9, 0xd9, 0x2e, 0x06, 0xfd, 0x6a, 0xb1, 0x11, 0x11, 0x90, 0x84, 0x0b, 0x1f, 0xa3, 0xa5, - 0x44, 0x2b, 0x37, 0x6c, 0x41, 0xa2, 0x30, 0x3b, 0x43, 0x2c, 0x64, 0x84, 0x35, 0x6c, 0x04, 0x72, - 0xd6, 0xce, 0x0b, 0xc9, 0x4e, 0x1b, 0x9e, 0x1a, 0x2a, 0xb2, 0xae, 0x61, 0x00, 0xb4, 0xa0, 0x25, - 0xea, 0xbe, 0xa0, 0xaf, 0x4a, 0xd7, 0x62, 0x23, 0xda, 0x20, 0x89, 0x4f, 0x48, 0x7c, 0x4c, 0x4d, - 0x0b, 0x5a, 0xa2, 0xde, 0x29, 0xe2, 0x4f, 0x85, 0x95, 0xc8, 0xdd, 0xda, 0x5f, 0x0a, 0x4a, 0xff, - 0x1b, 0xdc, 0xc2, 0xbc, 0xec, 0xa4, 0xb4, 0x98, 0xf9, 0xcf, 0x3f, 0x38, 0x57, 0x09, 0xf3, 0x77, - 0x05, 0x2d, 0x8f, 0xf8, 0xff, 0x5f, 0xff, 0x07, 0xf4, 0xfb, 0x67, 0x17, 0x95, 0xb9, 0xf3, 0x8b, - 0xca, 0xdc, 0x9f, 0x17, 0x95, 0xb9, 0xef, 0x82, 0x8a, 0x72, 0x16, 0x54, 0x94, 0xf3, 0xa0, 0xa2, - 0xfc, 0x1d, 0x54, 0x94, 0x1f, 0xfe, 0xa9, 0xcc, 0x7d, 0x59, 0x88, 0x78, 0xfe, 0x0d, 0x00, 0x00, - 0xff, 0xff, 0xef, 0x59, 0xca, 0xdd, 0x1e, 0x0f, 0x00, 0x00, + // 1224 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0xcd, 0x6e, 0x1c, 0x45, + 0x10, 0xf6, 0xec, 0xff, 0xf6, 0xc6, 0x8e, 0xd3, 0x90, 0x64, 0x59, 0xa4, 0x1d, 0x6b, 0x25, 0x90, + 0x13, 0x25, 0x33, 0x64, 0x13, 0x85, 0x90, 0x03, 0x52, 0xc6, 0x08, 0x81, 0xe5, 0x28, 0x56, 0xaf, + 0x43, 0x10, 0x04, 0x94, 0xde, 0xd9, 0xf6, 0xee, 0xc4, 0xf3, 0xc7, 0x74, 0xcf, 0x8a, 0xbd, 0xf1, + 0x08, 0xdc, 0x79, 0x00, 0x5e, 0x01, 0x21, 0xc4, 0x89, 0x43, 0xb8, 0xe5, 0xc0, 0x01, 0x2e, 0x23, + 0x32, 0xbc, 0x85, 0x4f, 0x68, 0x7a, 0x7a, 0x7e, 0xf6, 0xcf, 0xf1, 0x1a, 0xc9, 0x12, 0xb7, 0x99, + 0xea, 0xfa, 0xbe, 0xae, 0xae, 0xfa, 0xba, 0xaa, 0xc1, 0x07, 0x47, 0xf7, 0xa8, 0x62, 0x38, 0xea, + 0x91, 0xdf, 0x27, 0x9e, 0x4d, 0x18, 0xa1, 0xaa, 0x7b, 0x34, 0x54, 0xb1, 0x6b, 0x50, 0xb5, 0x8f, + 0x99, 0x3e, 0x52, 0xc7, 0x5d, 0x6c, 0xba, 0x23, 0x7c, 0x4b, 0x1d, 0x12, 0x9b, 0x78, 0x98, 0x91, + 0x81, 0xe2, 0x7a, 0x0e, 0x73, 0xe0, 0xb5, 0x18, 0xaa, 0x64, 0x50, 0xc5, 0x3d, 0x1a, 0x2a, 0x11, + 0x54, 0xe1, 0x50, 0x25, 0x81, 0xb6, 0x6e, 0x0e, 0x0d, 0x36, 0xf2, 0xfb, 0x8a, 0xee, 0x58, 0xea, + 0xd0, 0x19, 0x3a, 0x2a, 0x67, 0xe8, 0xfb, 0x87, 0xfc, 0x8f, 0xff, 0xf0, 0xaf, 0x98, 0xb9, 0x75, + 0x47, 0x04, 0x85, 0x5d, 0xc3, 0xc2, 0xfa, 0xc8, 0xb0, 0x89, 0x37, 0xc9, 0xc2, 0xb2, 0x08, 0xc3, + 0xea, 0x78, 0x2e, 0x9e, 0x96, 0xba, 0x0c, 0xe5, 0xf9, 0x36, 0x33, 0x2c, 0x32, 0x07, 0xb8, 0xfb, + 0x3a, 0x00, 0xd5, 0x47, 0xc4, 0xc2, 0x73, 0xb8, 0xdb, 0xcb, 0x70, 0x3e, 0x33, 0x4c, 0xd5, 0xb0, + 0x19, 0x65, 0xde, 0x1c, 0x28, 0x77, 0x26, 0x4a, 0xbc, 0x31, 0xf1, 0xb2, 0x03, 0x91, 0x6f, 0xb1, + 0xe5, 0x9a, 0x64, 0xd1, 0x99, 0x6e, 0x2c, 0x2d, 0xcf, 0x02, 0xef, 0xce, 0x8f, 0x05, 0x50, 0xdd, + 0xf1, 0x1c, 0x7b, 0xd7, 0xe9, 0xc3, 0x67, 0xa0, 0x16, 0x25, 0x6a, 0x80, 0x19, 0x6e, 0x4a, 0x5b, + 0xd2, 0x76, 0xa3, 0xfb, 0x9e, 0x22, 0x0a, 0x96, 0x8f, 0x3b, 0x2b, 0x59, 0xe4, 0xad, 0x8c, 0x6f, + 0x29, 0x8f, 0xfa, 0xcf, 0x89, 0xce, 0x1e, 0x12, 0x86, 0x35, 0xf8, 0x22, 0x90, 0xd7, 0xc2, 0x40, + 0x06, 0x99, 0x0d, 0xa5, 0xac, 0xf0, 0x73, 0x50, 0xa2, 0x2e, 0xd1, 0x9b, 0x05, 0xce, 0x7e, 0x57, + 0x39, 0xb5, 0x1c, 0x14, 0x11, 0x63, 0xcf, 0x25, 0xba, 0x76, 0x41, 0xec, 0x51, 0x8a, 0xfe, 0x10, + 0x67, 0x84, 0xcf, 0x40, 0x85, 0x32, 0xcc, 0x7c, 0xda, 0x2c, 0x72, 0xee, 0x7b, 0x67, 0xe0, 0xe6, + 0x78, 0x6d, 0x43, 0xb0, 0x57, 0xe2, 0x7f, 0x24, 0x78, 0x3b, 0xbf, 0x49, 0xa0, 0x21, 0x3c, 0xf7, + 0x0c, 0xca, 0xe0, 0xd3, 0xb9, 0x6c, 0x29, 0xa7, 0xcb, 0x56, 0x84, 0xe6, 0xb9, 0xda, 0x14, 0x3b, + 0xd5, 0x12, 0x4b, 0x2e, 0x53, 0x4f, 0x40, 0xd9, 0x60, 0xc4, 0xa2, 0xcd, 0xc2, 0x56, 0x71, 0xbb, + 0xd1, 0xed, 0xae, 0x7e, 0x1c, 0x6d, 0x5d, 0xd0, 0x97, 0x3f, 0x8d, 0x88, 0x50, 0xcc, 0xd7, 0xf9, + 0xb5, 0x94, 0x1e, 0x23, 0x4a, 0x1f, 0xbc, 0x01, 0x6a, 0x91, 0x66, 0x07, 0xbe, 0x49, 0xf8, 0x31, + 0xea, 0x59, 0x58, 0x3d, 0x61, 0x47, 0xa9, 0x07, 0x7c, 0x0c, 0xae, 0x52, 0x86, 0x3d, 0x66, 0xd8, + 0xc3, 0x8f, 0x08, 0x1e, 0x98, 0x86, 0x4d, 0x7a, 0x44, 0x77, 0xec, 0x01, 0xe5, 0x35, 0x2d, 0x6a, + 0x6f, 0x87, 0x81, 0x7c, 0xb5, 0xb7, 0xd8, 0x05, 0x2d, 0xc3, 0xc2, 0xa7, 0xe0, 0x92, 0xee, 0xd8, + 0xba, 0xef, 0x79, 0xc4, 0xd6, 0x27, 0xfb, 0x8e, 0x69, 0xe8, 0x13, 0x5e, 0xc8, 0xba, 0xa6, 0x88, + 0x68, 0x2e, 0xed, 0xcc, 0x3a, 0x1c, 0x2f, 0x32, 0xa2, 0x79, 0x22, 0xf8, 0x0e, 0xa8, 0x52, 0x9f, + 0xba, 0xc4, 0x1e, 0x34, 0x4b, 0x5b, 0xd2, 0x76, 0x4d, 0x6b, 0x84, 0x81, 0x5c, 0xed, 0xc5, 0x26, + 0x94, 0xac, 0xc1, 0x6f, 0x40, 0xe3, 0xb9, 0xd3, 0x3f, 0x20, 0x96, 0x6b, 0x62, 0x46, 0x9a, 0x65, + 0x5e, 0xd3, 0xfb, 0x2b, 0x24, 0x7e, 0x37, 0x43, 0x73, 0x9d, 0xbe, 0x21, 0x42, 0x6f, 0xe4, 0x16, + 0x50, 0x7e, 0x0f, 0xf8, 0x35, 0x68, 0x51, 0x5f, 0xd7, 0x09, 0xa5, 0x87, 0xbe, 0xb9, 0xeb, 0xf4, + 0xe9, 0x27, 0x06, 0x65, 0x8e, 0x37, 0xd9, 0x33, 0x2c, 0x83, 0x35, 0x2b, 0x5b, 0xd2, 0x76, 0x59, + 0x6b, 0x87, 0x81, 0xdc, 0xea, 0x2d, 0xf5, 0x42, 0x27, 0x30, 0x40, 0x04, 0xae, 0x1c, 0x62, 0xc3, + 0x24, 0x83, 0x39, 0xee, 0x2a, 0xe7, 0x6e, 0x85, 0x81, 0x7c, 0xe5, 0xe3, 0x85, 0x1e, 0x68, 0x09, + 0xb2, 0xf3, 0x87, 0x04, 0xd6, 0xa7, 0x6e, 0x0c, 0x7c, 0x0c, 0x2a, 0x58, 0x67, 0xc6, 0x38, 0x12, + 0x50, 0x24, 0xd6, 0x9b, 0xcb, 0x73, 0x96, 0x75, 0x0b, 0x44, 0x0e, 0x49, 0x54, 0x24, 0x92, 0x5d, + 0xb8, 0x07, 0x9c, 0x04, 0x09, 0x32, 0x68, 0x82, 0x4d, 0x13, 0x53, 0x96, 0xa8, 0xf0, 0xc0, 0xb0, + 0x08, 0xaf, 0x5f, 0xa3, 0x7b, 0xfd, 0x74, 0x17, 0x2d, 0x42, 0x68, 0x6f, 0x86, 0x81, 0xbc, 0xb9, + 0x37, 0xc3, 0x83, 0xe6, 0x98, 0x3b, 0x3f, 0x14, 0x40, 0xf1, 0x7c, 0x9a, 0xe0, 0xc1, 0x54, 0x13, + 0xec, 0xae, 0x26, 0xb0, 0xa5, 0x0d, 0xf0, 0xe9, 0x4c, 0x03, 0xbc, 0xb3, 0x22, 0xef, 0xc9, 0xcd, + 0xef, 0x65, 0x11, 0x5c, 0xd8, 0x75, 0xfa, 0x3b, 0x8e, 0x3d, 0x30, 0x98, 0xe1, 0xd8, 0xf0, 0x0e, + 0x28, 0xb1, 0x89, 0x9b, 0xb4, 0x8c, 0xad, 0x24, 0xa0, 0x83, 0x89, 0x4b, 0x8e, 0x03, 0x79, 0x33, + 0xef, 0x1b, 0xd9, 0x10, 0xf7, 0x86, 0x9f, 0xa5, 0x41, 0x16, 0x38, 0xee, 0xc3, 0xe9, 0xed, 0x8e, + 0x03, 0xf9, 0xc4, 0xe9, 0xa5, 0xa4, 0x9c, 0xd3, 0xe1, 0xc1, 0x21, 0x58, 0x8f, 0x0a, 0xba, 0xef, + 0x39, 0xfd, 0x58, 0x27, 0xc5, 0x95, 0x75, 0x72, 0x59, 0x84, 0xb2, 0xbe, 0x97, 0x27, 0x42, 0xd3, + 0xbc, 0x70, 0x0c, 0x60, 0x64, 0x38, 0xf0, 0xb0, 0x4d, 0xe3, 0xc3, 0x9d, 0x4d, 0x95, 0x2d, 0xb1, + 0x1b, 0xdc, 0x9b, 0x63, 0x43, 0x0b, 0x76, 0x80, 0xef, 0x82, 0x8a, 0x47, 0x30, 0x75, 0x6c, 0xde, + 0x96, 0xea, 0x59, 0x9d, 0x10, 0xb7, 0x22, 0xb1, 0x0a, 0xaf, 0x81, 0xaa, 0x45, 0x28, 0xc5, 0x43, + 0xc2, 0xbb, 0x47, 0x5d, 0xbb, 0x28, 0x1c, 0xab, 0x0f, 0x63, 0x33, 0x4a, 0xd6, 0x3b, 0xbf, 0x48, + 0xa0, 0x7a, 0x3e, 0xb3, 0xac, 0x37, 0x3d, 0xcb, 0x94, 0xd5, 0x94, 0xb9, 0x64, 0x8e, 0xfd, 0x54, + 0xe4, 0xe1, 0xf3, 0x19, 0x76, 0x0b, 0x34, 0x5c, 0xec, 0x61, 0xd3, 0x24, 0xa6, 0x41, 0x2d, 0x7e, + 0x82, 0xb2, 0x76, 0x31, 0xea, 0xbc, 0xfb, 0x99, 0x19, 0xe5, 0x7d, 0x22, 0x88, 0xee, 0x44, 0x4f, + 0xa8, 0x28, 0xc5, 0xb1, 0x1c, 0x05, 0x64, 0x27, 0x33, 0xa3, 0xbc, 0x0f, 0x7c, 0x04, 0x2e, 0xc7, + 0x9d, 0x69, 0x76, 0xf2, 0x15, 0xf9, 0xe4, 0x7b, 0x2b, 0x0c, 0xe4, 0xcb, 0x0f, 0x16, 0x39, 0xa0, + 0xc5, 0x38, 0xf8, 0x15, 0xa8, 0x51, 0x62, 0x12, 0x9d, 0x39, 0x9e, 0x90, 0xd0, 0xed, 0x53, 0x66, + 0x1d, 0xf7, 0x89, 0xd9, 0x13, 0x50, 0xed, 0x02, 0x9f, 0xd5, 0xe2, 0x0f, 0xa5, 0x94, 0xf0, 0x3e, + 0xd8, 0xb0, 0xb0, 0xed, 0xe3, 0xd4, 0x93, 0x6b, 0xa7, 0xa6, 0xc1, 0x30, 0x90, 0x37, 0x1e, 0x4e, + 0xad, 0xa0, 0x19, 0x4f, 0xf8, 0x25, 0xa8, 0xb1, 0x64, 0x10, 0x56, 0x78, 0x68, 0xaf, 0x69, 0xea, + 0xfb, 0xce, 0x60, 0x6a, 0xf6, 0xa5, 0x7a, 0x48, 0x07, 0x5f, 0x4a, 0xd8, 0xf9, 0xb9, 0x08, 0xea, + 0xd9, 0xf4, 0x38, 0x02, 0x40, 0x4f, 0xae, 0x35, 0x15, 0x13, 0xe4, 0xfd, 0xd5, 0x24, 0x92, 0xb6, + 0x85, 0xac, 0xf3, 0xa6, 0x26, 0x8a, 0x72, 0xf4, 0xf0, 0x09, 0xa8, 0xf3, 0x37, 0x08, 0xbf, 0xb6, + 0x85, 0x95, 0xaf, 0xed, 0x7a, 0x18, 0xc8, 0xf5, 0x5e, 0x42, 0x80, 0x32, 0x2e, 0x78, 0x08, 0x36, + 0x32, 0xad, 0x9c, 0xb1, 0x05, 0xf1, 0xc2, 0xec, 0x4c, 0xb1, 0xa0, 0x19, 0xd6, 0xa8, 0x11, 0x88, + 0x59, 0x5b, 0xe2, 0x92, 0x5d, 0x36, 0x3c, 0x55, 0x50, 0xe7, 0xef, 0x02, 0x32, 0x20, 0x03, 0x5e, + 0xf7, 0xb2, 0x76, 0x49, 0xb8, 0xd6, 0x7b, 0xc9, 0x02, 0xca, 0x7c, 0x22, 0xe2, 0x78, 0xe0, 0x8b, + 0x67, 0x47, 0x4a, 0x1c, 0x3f, 0x0f, 0x90, 0x58, 0xed, 0xfc, 0x25, 0x81, 0xfc, 0x7b, 0xe6, 0x1c, + 0xe6, 0xe5, 0x28, 0xa7, 0xc5, 0xc2, 0x7f, 0x7e, 0x94, 0x9d, 0x24, 0xcc, 0xdf, 0x25, 0x70, 0x71, + 0xc6, 0xff, 0xff, 0xfa, 0x1e, 0xd0, 0xae, 0xbf, 0x78, 0xd5, 0x5e, 0x7b, 0xf9, 0xaa, 0xbd, 0xf6, + 0xe7, 0xab, 0xf6, 0xda, 0x77, 0x61, 0x5b, 0x7a, 0x11, 0xb6, 0xa5, 0x97, 0x61, 0x5b, 0xfa, 0x3b, + 0x6c, 0x4b, 0xdf, 0xff, 0xd3, 0x5e, 0xfb, 0xa2, 0x96, 0xf0, 0xfc, 0x1b, 0x00, 0x00, 0xff, 0xff, + 0x0b, 0x15, 0xd8, 0x21, 0xd2, 0x0f, 0x00, 0x00, } diff --git a/pkg/apis/batch/v2alpha1/generated.proto b/pkg/apis/batch/v2alpha1/generated.proto index 6a842429ad7..1a58f867e8f 100644 --- a/pkg/apis/batch/v2alpha1/generated.proto +++ b/pkg/apis/batch/v2alpha1/generated.proto @@ -82,6 +82,16 @@ message CronJobSpec { // JobTemplate is the object that describes the job that will be created when // executing a CronJob. optional JobTemplateSpec jobTemplate = 5; + + // The number of successful finished jobs to retain. + // This is a pointer to distinguish between explicit zero and not specified. + // +optional + optional int32 successfulJobsHistoryLimit = 6; + + // The number of failed finished jobs to retain. + // This is a pointer to distinguish between explicit zero and not specified. + // +optional + optional int32 failedJobsHistoryLimit = 7; } // CronJobStatus represents the current state of a cron job. diff --git a/pkg/apis/batch/v2alpha1/types.generated.go b/pkg/apis/batch/v2alpha1/types.generated.go index 8efab7a6762..d6d0fe55bfb 100644 --- a/pkg/apis/batch/v2alpha1/types.generated.go +++ b/pkg/apis/batch/v2alpha1/types.generated.go @@ -3793,15 +3793,17 @@ func (x *CronJobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [5]bool + var yyq2 [7]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[1] = x.StartingDeadlineSeconds != nil yyq2[2] = x.ConcurrencyPolicy != "" yyq2[3] = x.Suspend != nil + yyq2[5] = x.SuccessfulJobsHistoryLimit != nil + yyq2[6] = x.FailedJobsHistoryLimit != nil var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(5) + r.EncodeArrayStart(7) } else { yynn2 = 2 for _, b := range yyq2 { @@ -3927,6 +3929,76 @@ func (x *CronJobSpec) CodecEncodeSelf(e *codec1978.Encoder) { yy22 := &x.JobTemplate yy22.CodecEncodeSelf(e) } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[5] { + if x.SuccessfulJobsHistoryLimit == nil { + r.EncodeNil() + } else { + yy25 := *x.SuccessfulJobsHistoryLimit + yym26 := z.EncBinary() + _ = yym26 + if false { + } else { + r.EncodeInt(int64(yy25)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[5] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("successfulJobsHistoryLimit")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.SuccessfulJobsHistoryLimit == nil { + r.EncodeNil() + } else { + yy27 := *x.SuccessfulJobsHistoryLimit + yym28 := z.EncBinary() + _ = yym28 + if false { + } else { + r.EncodeInt(int64(yy27)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[6] { + if x.FailedJobsHistoryLimit == nil { + r.EncodeNil() + } else { + yy30 := *x.FailedJobsHistoryLimit + yym31 := z.EncBinary() + _ = yym31 + if false { + } else { + r.EncodeInt(int64(yy30)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[6] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("failedJobsHistoryLimit")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.FailedJobsHistoryLimit == nil { + r.EncodeNil() + } else { + yy32 := *x.FailedJobsHistoryLimit + yym33 := z.EncBinary() + _ = yym33 + if false { + } else { + r.EncodeInt(int64(yy32)) + } + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -4046,6 +4118,38 @@ func (x *CronJobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { yyv11 := &x.JobTemplate yyv11.CodecDecodeSelf(d) } + case "successfulJobsHistoryLimit": + if r.TryDecodeAsNil() { + if x.SuccessfulJobsHistoryLimit != nil { + x.SuccessfulJobsHistoryLimit = nil + } + } else { + if x.SuccessfulJobsHistoryLimit == nil { + x.SuccessfulJobsHistoryLimit = new(int32) + } + yym13 := z.DecBinary() + _ = yym13 + if false { + } else { + *((*int32)(x.SuccessfulJobsHistoryLimit)) = int32(r.DecodeInt(32)) + } + } + case "failedJobsHistoryLimit": + if r.TryDecodeAsNil() { + if x.FailedJobsHistoryLimit != nil { + x.FailedJobsHistoryLimit = nil + } + } else { + if x.FailedJobsHistoryLimit == nil { + x.FailedJobsHistoryLimit = new(int32) + } + yym15 := z.DecBinary() + _ = yym15 + if false { + } else { + *((*int32)(x.FailedJobsHistoryLimit)) = int32(r.DecodeInt(32)) + } + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -4057,16 +4161,16 @@ func (x *CronJobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj12 int - var yyb12 bool - var yyhl12 bool = l >= 0 - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + var yyj16 int + var yyb16 bool + var yyhl16 bool = l >= 0 + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l } else { - yyb12 = r.CheckBreak() + yyb16 = r.CheckBreak() } - if yyb12 { + if yyb16 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4074,21 +4178,21 @@ func (x *CronJobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if r.TryDecodeAsNil() { x.Schedule = "" } else { - yyv13 := &x.Schedule - yym14 := z.DecBinary() - _ = yym14 + yyv17 := &x.Schedule + yym18 := z.DecBinary() + _ = yym18 if false { } else { - *((*string)(yyv13)) = r.DecodeString() + *((*string)(yyv17)) = r.DecodeString() } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l } else { - yyb12 = r.CheckBreak() + yyb16 = r.CheckBreak() } - if yyb12 { + if yyb16 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4101,20 +4205,20 @@ func (x *CronJobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.StartingDeadlineSeconds == nil { x.StartingDeadlineSeconds = new(int64) } - yym16 := z.DecBinary() - _ = yym16 + yym20 := z.DecBinary() + _ = yym20 if false { } else { *((*int64)(x.StartingDeadlineSeconds)) = int64(r.DecodeInt(64)) } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l } else { - yyb12 = r.CheckBreak() + yyb16 = r.CheckBreak() } - if yyb12 { + if yyb16 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4122,16 +4226,16 @@ func (x *CronJobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if r.TryDecodeAsNil() { x.ConcurrencyPolicy = "" } else { - yyv17 := &x.ConcurrencyPolicy - yyv17.CodecDecodeSelf(d) + yyv21 := &x.ConcurrencyPolicy + yyv21.CodecDecodeSelf(d) } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l } else { - yyb12 = r.CheckBreak() + yyb16 = r.CheckBreak() } - if yyb12 { + if yyb16 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4144,20 +4248,20 @@ func (x *CronJobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.Suspend == nil { x.Suspend = new(bool) } - yym19 := z.DecBinary() - _ = yym19 + yym23 := z.DecBinary() + _ = yym23 if false { } else { *((*bool)(x.Suspend)) = r.DecodeBool() } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l } else { - yyb12 = r.CheckBreak() + yyb16 = r.CheckBreak() } - if yyb12 { + if yyb16 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4165,21 +4269,73 @@ func (x *CronJobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if r.TryDecodeAsNil() { x.JobTemplate = JobTemplateSpec{} } else { - yyv20 := &x.JobTemplate - yyv20.CodecDecodeSelf(d) + yyv24 := &x.JobTemplate + yyv24.CodecDecodeSelf(d) + } + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l + } else { + yyb16 = r.CheckBreak() + } + if yyb16 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.SuccessfulJobsHistoryLimit != nil { + x.SuccessfulJobsHistoryLimit = nil + } + } else { + if x.SuccessfulJobsHistoryLimit == nil { + x.SuccessfulJobsHistoryLimit = new(int32) + } + yym26 := z.DecBinary() + _ = yym26 + if false { + } else { + *((*int32)(x.SuccessfulJobsHistoryLimit)) = int32(r.DecodeInt(32)) + } + } + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l + } else { + yyb16 = r.CheckBreak() + } + if yyb16 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.FailedJobsHistoryLimit != nil { + x.FailedJobsHistoryLimit = nil + } + } else { + if x.FailedJobsHistoryLimit == nil { + x.FailedJobsHistoryLimit = new(int32) + } + yym28 := z.DecBinary() + _ = yym28 + if false { + } else { + *((*int32)(x.FailedJobsHistoryLimit)) = int32(r.DecodeInt(32)) + } } for { - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj16++ + if yyhl16 { + yyb16 = yyj16 > l } else { - yyb12 = r.CheckBreak() + yyb16 = r.CheckBreak() } - if yyb12 { + if yyb16 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj12-1, "") + z.DecStructFieldNotFound(yyj16-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -4772,7 +4928,7 @@ func (x codecSelfer1234) decSliceCronJob(v *[]CronJob, d *codec1978.Decoder) { yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 1128) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 1144) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/pkg/apis/batch/v2alpha1/types.go b/pkg/apis/batch/v2alpha1/types.go index e03f8c0561d..5d111471f2d 100644 --- a/pkg/apis/batch/v2alpha1/types.go +++ b/pkg/apis/batch/v2alpha1/types.go @@ -250,6 +250,16 @@ type CronJobSpec struct { // JobTemplate is the object that describes the job that will be created when // executing a CronJob. JobTemplate JobTemplateSpec `json:"jobTemplate" protobuf:"bytes,5,opt,name=jobTemplate"` + + // The number of successful finished jobs to retain. + // This is a pointer to distinguish between explicit zero and not specified. + // +optional + SuccessfulJobsHistoryLimit *int32 `json:"successfulJobsHistoryLimit,omitempty" protobuf:"varint,6,opt,name=successfulJobsHistoryLimit"` + + // The number of failed finished jobs to retain. + // This is a pointer to distinguish between explicit zero and not specified. + // +optional + FailedJobsHistoryLimit *int32 `json:"failedJobsHistoryLimit,omitempty" protobuf:"varint,7,opt,name=failedJobsHistoryLimit"` } // ConcurrencyPolicy describes how the job will be handled. diff --git a/pkg/apis/batch/v2alpha1/types_swagger_doc_generated.go b/pkg/apis/batch/v2alpha1/types_swagger_doc_generated.go index 6e8c40a0feb..0140e92d3ec 100644 --- a/pkg/apis/batch/v2alpha1/types_swagger_doc_generated.go +++ b/pkg/apis/batch/v2alpha1/types_swagger_doc_generated.go @@ -49,12 +49,14 @@ func (CronJobList) SwaggerDoc() map[string]string { } var map_CronJobSpec = map[string]string{ - "": "CronJobSpec describes how the job execution will look like and when it will actually run.", - "schedule": "Schedule contains the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.", - "startingDeadlineSeconds": "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", - "concurrencyPolicy": "ConcurrencyPolicy specifies how to treat concurrent executions of a Job.", - "suspend": "Suspend flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", - "jobTemplate": "JobTemplate is the object that describes the job that will be created when executing a CronJob.", + "": "CronJobSpec describes how the job execution will look like and when it will actually run.", + "schedule": "Schedule contains the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.", + "startingDeadlineSeconds": "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", + "concurrencyPolicy": "ConcurrencyPolicy specifies how to treat concurrent executions of a Job.", + "suspend": "Suspend flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", + "jobTemplate": "JobTemplate is the object that describes the job that will be created when executing a CronJob.", + "successfulJobsHistoryLimit": "The number of successful finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", + "failedJobsHistoryLimit": "The number of failed finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", } func (CronJobSpec) SwaggerDoc() map[string]string { diff --git a/pkg/apis/batch/v2alpha1/zz_generated.conversion.go b/pkg/apis/batch/v2alpha1/zz_generated.conversion.go index efea0a5aa0a..428f5c8849e 100644 --- a/pkg/apis/batch/v2alpha1/zz_generated.conversion.go +++ b/pkg/apis/batch/v2alpha1/zz_generated.conversion.go @@ -141,6 +141,8 @@ func autoConvert_v2alpha1_CronJobSpec_To_batch_CronJobSpec(in *CronJobSpec, out if err := Convert_v2alpha1_JobTemplateSpec_To_batch_JobTemplateSpec(&in.JobTemplate, &out.JobTemplate, s); err != nil { return err } + out.SuccessfulJobsHistoryLimit = (*int32)(unsafe.Pointer(in.SuccessfulJobsHistoryLimit)) + out.FailedJobsHistoryLimit = (*int32)(unsafe.Pointer(in.FailedJobsHistoryLimit)) return nil } @@ -156,6 +158,8 @@ func autoConvert_batch_CronJobSpec_To_v2alpha1_CronJobSpec(in *batch.CronJobSpec if err := Convert_batch_JobTemplateSpec_To_v2alpha1_JobTemplateSpec(&in.JobTemplate, &out.JobTemplate, s); err != nil { return err } + out.SuccessfulJobsHistoryLimit = (*int32)(unsafe.Pointer(in.SuccessfulJobsHistoryLimit)) + out.FailedJobsHistoryLimit = (*int32)(unsafe.Pointer(in.FailedJobsHistoryLimit)) return nil } diff --git a/pkg/apis/batch/v2alpha1/zz_generated.deepcopy.go b/pkg/apis/batch/v2alpha1/zz_generated.deepcopy.go index 09bd21ab1b4..1d89e93b0a3 100644 --- a/pkg/apis/batch/v2alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/batch/v2alpha1/zz_generated.deepcopy.go @@ -106,6 +106,16 @@ func DeepCopy_v2alpha1_CronJobSpec(in interface{}, out interface{}, c *conversio if err := DeepCopy_v2alpha1_JobTemplateSpec(&in.JobTemplate, &out.JobTemplate, c); err != nil { return err } + if in.SuccessfulJobsHistoryLimit != nil { + in, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit + *out = new(int32) + **out = **in + } + if in.FailedJobsHistoryLimit != nil { + in, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit + *out = new(int32) + **out = **in + } return nil } } diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index 51f6b250a0c..77ab204e4a7 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -179,6 +179,15 @@ func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path) field.Err allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...) allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"))...) + if spec.SuccessfulJobsHistoryLimit != nil { + // zero is a valid SuccessfulJobsHistoryLimit + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.SuccessfulJobsHistoryLimit), fldPath.Child("successfulJobsHistoryLimit"))...) + } + if spec.FailedJobsHistoryLimit != nil { + // zero is a valid SuccessfulJobsHistoryLimit + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.FailedJobsHistoryLimit), fldPath.Child("failedJobsHistoryLimit"))...) + } + return allErrs } diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index 0e60991f423..53c81052999 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -402,6 +402,40 @@ func TestValidateCronJob(t *testing.T) { }, }, }, + "spec.successfulJobsHistoryLimit: must be greater than or equal to 0": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "* * * * ?", + ConcurrencyPolicy: batch.AllowConcurrent, + SuccessfulJobsHistoryLimit: &negative, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.failedJobsHistoryLimit: must be greater than or equal to 0": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "* * * * ?", + ConcurrencyPolicy: batch.AllowConcurrent, + FailedJobsHistoryLimit: &negative, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, "spec.concurrencyPolicy: Required value": { ObjectMeta: metav1.ObjectMeta{ Name: "mycronjob", diff --git a/pkg/apis/batch/zz_generated.deepcopy.go b/pkg/apis/batch/zz_generated.deepcopy.go index b620c81422b..8e3ff29e75f 100644 --- a/pkg/apis/batch/zz_generated.deepcopy.go +++ b/pkg/apis/batch/zz_generated.deepcopy.go @@ -106,6 +106,16 @@ func DeepCopy_batch_CronJobSpec(in interface{}, out interface{}, c *conversion.C if err := DeepCopy_batch_JobTemplateSpec(&in.JobTemplate, &out.JobTemplate, c); err != nil { return err } + if in.SuccessfulJobsHistoryLimit != nil { + in, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit + *out = new(int32) + **out = **in + } + if in.FailedJobsHistoryLimit != nil { + in, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit + *out = new(int32) + **out = **in + } return nil } } diff --git a/pkg/controller/cronjob/cronjob_controller.go b/pkg/controller/cronjob/cronjob_controller.go index 173e2a25d31..9271f2242b0 100644 --- a/pkg/controller/cronjob/cronjob_controller.go +++ b/pkg/controller/cronjob/cronjob_controller.go @@ -30,6 +30,7 @@ Just periodically list jobs and SJs, and then reconcile them. import ( "fmt" + "sort" "time" "github.com/golang/glog" @@ -92,13 +93,13 @@ func (jm *CronJobController) Run(stopCh <-chan struct{}) { defer utilruntime.HandleCrash() glog.Infof("Starting CronJob Manager") // Check things every 10 second. - go wait.Until(jm.SyncAll, 10*time.Second, stopCh) + go wait.Until(jm.syncAll, 10*time.Second, stopCh) <-stopCh glog.Infof("Shutting down CronJob Manager") } -// SyncAll lists all the CronJobs and Jobs and reconciles them. -func (jm *CronJobController) SyncAll() { +// syncAll lists all the CronJobs and Jobs and reconciles them. +func (jm *CronJobController) syncAll() { sjl, err := jm.kubeClient.BatchV2alpha1().CronJobs(metav1.NamespaceAll).List(metav1.ListOptions{}) if err != nil { glog.Errorf("Error listing cronjobs: %v", err) @@ -119,24 +120,86 @@ func (jm *CronJobController) SyncAll() { glog.V(4).Infof("Found %d groups", len(jobsBySj)) for _, sj := range sjs { - SyncOne(sj, jobsBySj[sj.UID], time.Now(), jm.jobControl, jm.sjControl, jm.podControl, jm.recorder) + syncOne(&sj, jobsBySj[sj.UID], time.Now(), jm.jobControl, jm.sjControl, jm.podControl, jm.recorder) + cleanupFinishedJobs(&sj, jobsBySj[sj.UID], jm.jobControl, jm.sjControl, jm.podControl, jm.recorder) } } -// SyncOne reconciles a CronJob with a list of any Jobs that it created. +// cleanupFinishedJobs cleanups finished jobs created by a CronJob +func cleanupFinishedJobs(sj *batch.CronJob, js []batch.Job, jc jobControlInterface, sjc sjControlInterface, pc podControlInterface, recorder record.EventRecorder) { + // If neither limits are active, there is no need to do anything. + if sj.Spec.FailedJobsHistoryLimit == nil && sj.Spec.SuccessfulJobsHistoryLimit == nil { + return + } + + failedJobs := []batch.Job{} + succesfulJobs := []batch.Job{} + + for _, job := range js { + isFinished, finishedStatus := getFinishedStatus(&job) + if isFinished && finishedStatus == batch.JobComplete { + succesfulJobs = append(succesfulJobs, job) + } else if isFinished && finishedStatus == batch.JobFailed { + failedJobs = append(failedJobs, job) + } + } + + if sj.Spec.SuccessfulJobsHistoryLimit != nil { + removeOldestJobs(sj, + succesfulJobs, + jc, + pc, + *sj.Spec.SuccessfulJobsHistoryLimit, + recorder) + } + + if sj.Spec.FailedJobsHistoryLimit != nil { + removeOldestJobs(sj, + failedJobs, + jc, + pc, + *sj.Spec.FailedJobsHistoryLimit, + recorder) + } + + // Update the CronJob, in case jobs were removed from the list. + if _, err := sjc.UpdateStatus(sj); err != nil { + nameForLog := fmt.Sprintf("%s/%s", sj.Namespace, sj.Name) + glog.Infof("Unable to update status for %s (rv = %s): %v", nameForLog, sj.ResourceVersion, err) + } +} + +// removeOldestJobs removes the oldest jobs from a list of jobs +func removeOldestJobs(sj *batch.CronJob, js []batch.Job, jc jobControlInterface, pc podControlInterface, maxJobs int32, recorder record.EventRecorder) { + numToDelete := len(js) - int(maxJobs) + if numToDelete <= 0 { + return + } + + nameForLog := fmt.Sprintf("%s/%s", sj.Namespace, sj.Name) + glog.V(4).Infof("Cleaning up %d/%d jobs from %s", numToDelete, len(js), nameForLog) + + sort.Sort(byJobStartTime(js)) + for i := 0; i < numToDelete; i++ { + glog.V(4).Infof("Removing job %s from %s", js[i].Name, nameForLog) + deleteJob(sj, &js[i], jc, pc, recorder, "history limit reached") + } +} + +// syncOne reconciles a CronJob with a list of any Jobs that it created. // All known jobs created by "sj" should be included in "js". // The current time is passed in to facilitate testing. // It has no receiver, to facilitate testing. -func SyncOne(sj batch.CronJob, js []batch.Job, now time.Time, jc jobControlInterface, sjc sjControlInterface, pc podControlInterface, recorder record.EventRecorder) { +func syncOne(sj *batch.CronJob, js []batch.Job, now time.Time, jc jobControlInterface, sjc sjControlInterface, pc podControlInterface, recorder record.EventRecorder) { nameForLog := fmt.Sprintf("%s/%s", sj.Namespace, sj.Name) childrenJobs := make(map[types.UID]bool) for i := range js { j := js[i] childrenJobs[j.ObjectMeta.UID] = true - found := inActiveList(sj, j.ObjectMeta.UID) + found := inActiveList(*sj, j.ObjectMeta.UID) if !found && !IsJobFinished(&j) { - recorder.Eventf(&sj, v1.EventTypeWarning, "UnexpectedJob", "Saw a job that the controller did not create or forgot: %v", j.Name) + recorder.Eventf(sj, v1.EventTypeWarning, "UnexpectedJob", "Saw a job that the controller did not create or forgot: %v", j.Name) // We found an unfinished job that has us as the parent, but it is not in our Active list. // This could happen if we crashed right after creating the Job and before updating the status, // or if our jobs list is newer than our sj status after a relist, or if someone intentionally created @@ -148,9 +211,9 @@ func SyncOne(sj batch.CronJob, js []batch.Job, now time.Time, jc jobControlInter // in the same namespace "adopt" that job. ReplicaSets and their Pods work the same way. // TBS: how to update sj.Status.LastScheduleTime if the adopted job is newer than any we knew about? } else if found && IsJobFinished(&j) { - deleteFromActiveList(&sj, j.ObjectMeta.UID) + deleteFromActiveList(sj, j.ObjectMeta.UID) // TODO: event to call out failure vs success. - recorder.Eventf(&sj, v1.EventTypeNormal, "SawCompletedJob", "Saw completed job: %v", j.Name) + recorder.Eventf(sj, v1.EventTypeNormal, "SawCompletedJob", "Saw completed job: %v", j.Name) } } @@ -159,25 +222,25 @@ func SyncOne(sj batch.CronJob, js []batch.Job, now time.Time, jc jobControlInter // job running. for _, j := range sj.Status.Active { if found := childrenJobs[j.UID]; !found { - recorder.Eventf(&sj, v1.EventTypeNormal, "MissingJob", "Active job went missing: %v", j.Name) - deleteFromActiveList(&sj, j.UID) + recorder.Eventf(sj, v1.EventTypeNormal, "MissingJob", "Active job went missing: %v", j.Name) + deleteFromActiveList(sj, j.UID) } } - updatedSJ, err := sjc.UpdateStatus(&sj) + updatedSJ, err := sjc.UpdateStatus(sj) if err != nil { glog.Errorf("Unable to update status for %s (rv = %s): %v", nameForLog, sj.ResourceVersion, err) return } - sj = *updatedSJ + *sj = *updatedSJ if sj.Spec.Suspend != nil && *sj.Spec.Suspend { glog.V(4).Infof("Not starting job for %s because it is suspended", nameForLog) return } - times, err := getRecentUnmetScheduleTimes(sj, now) + times, err := getRecentUnmetScheduleTimes(*sj, now) if err != nil { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedNeedsStart", "Cannot determine if job needs to be started: %v", err) + recorder.Eventf(sj, v1.EventTypeWarning, "FailedNeedsStart", "Cannot determine if job needs to be started: %v", err) glog.Errorf("Cannot determine if %s needs to be started: %v", nameForLog, err) } // TODO: handle multiple unmet start times, from oldest to newest, updating status as needed. @@ -224,73 +287,37 @@ func SyncOne(sj batch.CronJob, js []batch.Job, now time.Time, jc jobControlInter // TODO: this should be replaced with server side job deletion // currently this mimics JobReaper from pkg/kubectl/stop.go glog.V(4).Infof("Deleting job %s of %s that was still running at next scheduled start time", j.Name, nameForLog) + job, err := jc.GetJob(j.Namespace, j.Name) if err != nil { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedGet", "Get job: %v", err) + recorder.Eventf(sj, v1.EventTypeWarning, "FailedGet", "Get job: %v", err) return } - // scale job down to 0 - if *job.Spec.Parallelism != 0 { - zero := int32(0) - job.Spec.Parallelism = &zero - job, err = jc.UpdateJob(job.Namespace, job) - if err != nil { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedUpdate", "Update job: %v", err) - return - } - } - // remove all pods... - selector, _ := metav1.LabelSelectorAsSelector(job.Spec.Selector) - options := metav1.ListOptions{LabelSelector: selector.String()} - podList, err := pc.ListPods(job.Namespace, options) - if err != nil { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedList", "List job-pods: %v", err) - } - errList := []error{} - for _, pod := range podList.Items { - glog.V(2).Infof("CronJob controller is deleting Pod %v/%v", pod.Namespace, pod.Name) - if err := pc.DeletePod(pod.Namespace, pod.Name); err != nil { - // ignores the error when the pod isn't found - if !errors.IsNotFound(err) { - errList = append(errList, err) - } - } - } - if len(errList) != 0 { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedDelete", "Deleted job-pods: %v", utilerrors.NewAggregate(errList)) + if !deleteJob(sj, job, jc, pc, recorder, "") { return } - // ... the job itself... - if err := jc.DeleteJob(job.Namespace, job.Name); err != nil { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedDelete", "Deleted job: %v", err) - glog.Errorf("Error deleting job %s from %s: %v", job.Name, nameForLog, err) - return - } - // ... and its reference from active list - deleteFromActiveList(&sj, job.ObjectMeta.UID) - recorder.Eventf(&sj, v1.EventTypeNormal, "SuccessfulDelete", "Deleted job %v", j.Name) } } - jobReq, err := getJobFromTemplate(&sj, scheduledTime) + jobReq, err := getJobFromTemplate(sj, scheduledTime) if err != nil { glog.Errorf("Unable to make Job from template in %s: %v", nameForLog, err) return } jobResp, err := jc.CreateJob(sj.Namespace, jobReq) if err != nil { - recorder.Eventf(&sj, v1.EventTypeWarning, "FailedCreate", "Error creating job: %v", err) + recorder.Eventf(sj, v1.EventTypeWarning, "FailedCreate", "Error creating job: %v", err) return } glog.V(4).Infof("Created Job %s for %s", jobResp.Name, nameForLog) - recorder.Eventf(&sj, v1.EventTypeNormal, "SuccessfulCreate", "Created job %v", jobResp.Name) + recorder.Eventf(sj, v1.EventTypeNormal, "SuccessfulCreate", "Created job %v", jobResp.Name) // ------------------------------------------------------------------ // // If this process restarts at this point (after posting a job, but // before updating the status), then we might try to start the job on // the next time. Actually, if we relist the SJs and Jobs on the next - // iteration of SyncAll, we might not see our own status update, and + // iteration of syncAll, we might not see our own status update, and // then post one again. So, we need to use the job name as a lock to // prevent us from making the job twice (name the job with hash of its // scheduled time). @@ -303,13 +330,64 @@ func SyncOne(sj batch.CronJob, js []batch.Job, now time.Time, jc jobControlInter sj.Status.Active = append(sj.Status.Active, *ref) } sj.Status.LastScheduleTime = &metav1.Time{Time: scheduledTime} - if _, err := sjc.UpdateStatus(&sj); err != nil { + if _, err := sjc.UpdateStatus(sj); err != nil { glog.Infof("Unable to update status for %s (rv = %s): %v", nameForLog, sj.ResourceVersion, err) } return } +// deleteJob reaps a job, deleting the job, the pobs and the reference in the active list +func deleteJob(sj *batch.CronJob, job *batch.Job, jc jobControlInterface, pc podControlInterface, recorder record.EventRecorder, reason string) bool { + // TODO: this should be replaced with server side job deletion + // currencontinuetly this mimics JobReaper from pkg/kubectl/stop.go + nameForLog := fmt.Sprintf("%s/%s", sj.Namespace, sj.Name) + var err error + + // scale job down to 0 + if *job.Spec.Parallelism != 0 { + zero := int32(0) + job.Spec.Parallelism = &zero + job, err = jc.UpdateJob(job.Namespace, job) + if err != nil { + recorder.Eventf(sj, v1.EventTypeWarning, "FailedUpdate", "Update job: %v", err) + return false + } + } + // remove all pods... + selector, _ := metav1.LabelSelectorAsSelector(job.Spec.Selector) + options := metav1.ListOptions{LabelSelector: selector.String()} + podList, err := pc.ListPods(job.Namespace, options) + if err != nil { + recorder.Eventf(sj, v1.EventTypeWarning, "FailedList", "List job-pods: %v", err) + } + errList := []error{} + for _, pod := range podList.Items { + glog.V(2).Infof("CronJob controller is deleting Pod %v/%v", pod.Namespace, pod.Name) + if err := pc.DeletePod(pod.Namespace, pod.Name); err != nil { + // ignores the error when the pod isn't found + if !errors.IsNotFound(err) { + errList = append(errList, err) + } + } + } + if len(errList) != 0 { + recorder.Eventf(sj, v1.EventTypeWarning, "FailedDelete", "Deleted job-pods: %v", utilerrors.NewAggregate(errList)) + return false + } + // ... the job itself... + if err := jc.DeleteJob(job.Namespace, job.Name); err != nil { + recorder.Eventf(sj, v1.EventTypeWarning, "FailedDelete", "Deleted job: %v", err) + glog.Errorf("Error deleting job %s from %s: %v", job.Name, nameForLog, err) + return false + } + // ... and its reference from active list + deleteFromActiveList(sj, job.ObjectMeta.UID) + recorder.Eventf(sj, v1.EventTypeNormal, "SuccessfulDelete", "Deleted job %v", job.Name) + + return true +} + func getRef(object runtime.Object) (*v1.ObjectReference, error) { return v1.GetReference(api.Scheme, object) } diff --git a/pkg/controller/cronjob/cronjob_controller_test.go b/pkg/controller/cronjob/cronjob_controller_test.go index f3843f77b41..bcbe2b5f5aa 100644 --- a/pkg/controller/cronjob/cronjob_controller_test.go +++ b/pkg/controller/cronjob/cronjob_controller_test.go @@ -17,6 +17,8 @@ limitations under the License. package cronjob import ( + "sort" + "strconv" "strings" "testing" "time" @@ -81,6 +83,14 @@ func justAfterThePriorHour() time.Time { return T1 } +func startTimeStringToTime(startTime string) time.Time { + T1, err := time.Parse(time.RFC3339, startTime) + if err != nil { + panic("test setup error") + } + return T1 +} + // returns a cronJob with some fields filled in. func cronJob() batch.CronJob { return batch.CronJob{ @@ -270,7 +280,7 @@ func TestSyncOne_RunOrNot(t *testing.T) { pc := &fakePodControl{} recorder := record.NewFakeRecorder(10) - SyncOne(sj, js, tc.now, jc, sjc, pc, recorder) + syncOne(&sj, js, tc.now, jc, sjc, pc, recorder) expectedCreates := 0 if tc.expectCreate { expectedCreates = 1 @@ -320,10 +330,237 @@ func TestSyncOne_RunOrNot(t *testing.T) { } } +type CleanupJobSpec struct { + StartTime string + IsFinished bool + IsSuccessful bool + ExpectDelete bool + IsStillInActiveList bool // only when IsFinished is set +} + +func TestCleanupFinishedJobs_DeleteOrNot(t *testing.T) { + limitThree := int32(3) + limitTwo := int32(2) + limitOne := int32(1) + limitZero := int32(0) + + // Starting times are assumed to be sorted by increasing start time + // in all the test cases + testCases := map[string]struct { + jobSpecs []CleanupJobSpec + now time.Time + successfulJobsHistoryLimit *int32 + failedJobsHistoryLimit *int32 + expectActive int + }{ + "success. job limit reached": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, T, F}, + {"2016-05-19T05:00:00Z", T, T, T, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", F, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), &limitTwo, &limitOne, 1}, + + "success. jobs not processed by Sync yet": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, T, F}, + {"2016-05-19T05:00:00Z", T, T, T, T}, + {"2016-05-19T06:00:00Z", T, T, F, T}, + {"2016-05-19T07:00:00Z", T, T, F, T}, + {"2016-05-19T08:00:00Z", F, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, T}, + }, justBeforeTheHour(), &limitTwo, &limitOne, 4}, + + "failed job limit reached": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, F, T, F}, + {"2016-05-19T05:00:00Z", T, F, T, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", T, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), &limitTwo, &limitTwo, 0}, + + "success. job limit set to zero": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, T, F}, + {"2016-05-19T05:00:00Z", T, F, T, F}, + {"2016-05-19T06:00:00Z", T, T, T, F}, + {"2016-05-19T07:00:00Z", T, T, T, F}, + {"2016-05-19T08:00:00Z", F, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), &limitZero, &limitOne, 1}, + + "failed job limit set to zero": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, F, F}, + {"2016-05-19T05:00:00Z", T, F, T, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", F, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, T, F}, + }, justBeforeTheHour(), &limitThree, &limitZero, 1}, + + "no limits reached": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, F, F}, + {"2016-05-19T05:00:00Z", T, F, F, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", T, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), &limitThree, &limitThree, 0}, + + // This test case should trigger the short-circuit + "limits disabled": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, F, F}, + {"2016-05-19T05:00:00Z", T, F, F, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", T, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), nil, nil, 0}, + + "success limit disabled": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, F, F}, + {"2016-05-19T05:00:00Z", T, F, F, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", T, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), nil, &limitThree, 0}, + + "failure limit disabled": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", T, T, F, F}, + {"2016-05-19T05:00:00Z", T, F, F, F}, + {"2016-05-19T06:00:00Z", T, T, F, F}, + {"2016-05-19T07:00:00Z", T, T, F, F}, + {"2016-05-19T08:00:00Z", T, F, F, F}, + {"2016-05-19T09:00:00Z", T, F, F, F}, + }, justBeforeTheHour(), &limitThree, nil, 0}, + + "no limits reached because still active": { + []CleanupJobSpec{ + {"2016-05-19T04:00:00Z", F, F, F, F}, + {"2016-05-19T05:00:00Z", F, F, F, F}, + {"2016-05-19T06:00:00Z", F, F, F, F}, + {"2016-05-19T07:00:00Z", F, F, F, F}, + {"2016-05-19T08:00:00Z", F, F, F, F}, + {"2016-05-19T09:00:00Z", F, F, F, F}, + }, justBeforeTheHour(), &limitZero, &limitZero, 6}, + } + + for name, tc := range testCases { + sj := cronJob() + suspend := false + sj.Spec.ConcurrencyPolicy = f + sj.Spec.Suspend = &suspend + sj.Spec.Schedule = onTheHour + + sj.Spec.SuccessfulJobsHistoryLimit = tc.successfulJobsHistoryLimit + sj.Spec.FailedJobsHistoryLimit = tc.failedJobsHistoryLimit + + var ( + job *batch.Job + err error + ) + + // Set consistent timestamps for the CronJob + if len(tc.jobSpecs) != 0 { + firstTime := startTimeStringToTime(tc.jobSpecs[0].StartTime) + lastTime := startTimeStringToTime(tc.jobSpecs[len(tc.jobSpecs)-1].StartTime) + sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: firstTime} + sj.Status.LastScheduleTime = &metav1.Time{Time: lastTime} + } else { + sj.ObjectMeta.CreationTimestamp = metav1.Time{Time: justBeforeTheHour()} + } + + // Create jobs + js := []batch.Job{} + jobsToDelete := []string{} + sj.Status.Active = []v1.ObjectReference{} + + for i, spec := range tc.jobSpecs { + job, err = getJobFromTemplate(&sj, startTimeStringToTime(spec.StartTime)) + if err != nil { + t.Fatalf("%s: unexpected error creating a job from template: %v", name, err) + } + + job.UID = types.UID(strconv.Itoa(i)) + job.Namespace = "" + + if spec.IsFinished { + var conditionType batch.JobConditionType + if spec.IsSuccessful { + conditionType = batch.JobComplete + } else { + conditionType = batch.JobFailed + } + condition := batch.JobCondition{Type: conditionType, Status: v1.ConditionTrue} + job.Status.Conditions = append(job.Status.Conditions, condition) + + if spec.IsStillInActiveList { + sj.Status.Active = append(sj.Status.Active, v1.ObjectReference{UID: job.UID}) + } + } else { + if spec.IsSuccessful || spec.IsStillInActiveList { + t.Errorf("%s: test setup error: this case makes no sense", name) + } + sj.Status.Active = append(sj.Status.Active, v1.ObjectReference{UID: job.UID}) + } + + js = append(js, *job) + if spec.ExpectDelete { + jobsToDelete = append(jobsToDelete, job.Name) + } + } + + jc := &fakeJobControl{Job: job} + pc := &fakePodControl{} + sjc := &fakeSJControl{} + recorder := record.NewFakeRecorder(10) + + cleanupFinishedJobs(&sj, js, jc, sjc, pc, recorder) + + // Check we have actually deleted the correct jobs + if len(jc.DeleteJobName) != len(jobsToDelete) { + t.Errorf("%s: expected %d job deleted, actually %d", name, len(jobsToDelete), len(jc.DeleteJobName)) + } else { + sort.Strings(jobsToDelete) + sort.Strings(jc.DeleteJobName) + for i, expectedJobName := range jobsToDelete { + if expectedJobName != jc.DeleteJobName[i] { + t.Errorf("%s: expected job %s deleted, actually %v -- %v vs %v", name, expectedJobName, jc.DeleteJobName[i], jc.DeleteJobName, jobsToDelete) + } + } + } + + // Check for events + expectedEvents := len(jobsToDelete) + if len(recorder.Events) != expectedEvents { + t.Errorf("%s: expected %d event, actually %v", name, expectedEvents, len(recorder.Events)) + } + + // Check for jobs still in active list + numActive := 0 + if len(sjc.Updates) != 0 { + numActive = len(sjc.Updates[len(sjc.Updates)-1].Status.Active) + } + if tc.expectActive != numActive { + t.Errorf("%s: expected Active size %d, got %d", name, tc.expectActive, numActive) + } + } +} + // TODO: simulation where the controller randomly doesn't run, and randomly has errors starting jobs or deleting jobs, // but over time, all jobs run as expected (assuming Allow and no deadline). -// TestSyncOne_Status tests sj.UpdateStatus in SyncOne +// TestSyncOne_Status tests sj.UpdateStatus in syncOne func TestSyncOne_Status(t *testing.T) { finishedJob := newJob("1") finishedJob.Status.Conditions = append(finishedJob.Status.Conditions, batch.JobCondition{Type: batch.JobComplete, Status: v1.ConditionTrue}) @@ -443,7 +680,7 @@ func TestSyncOne_Status(t *testing.T) { recorder := record.NewFakeRecorder(10) // Run the code - SyncOne(sj, jobs, tc.now, jc, sjc, pc, recorder) + syncOne(&sj, jobs, tc.now, jc, sjc, pc, recorder) // Status update happens once when ranging through job list, and another one if create jobs. expectUpdates := 1 diff --git a/pkg/controller/cronjob/utils.go b/pkg/controller/cronjob/utils.go index a2837b2d535..2fc73a666e5 100644 --- a/pkg/controller/cronjob/utils.go +++ b/pkg/controller/cronjob/utils.go @@ -234,11 +234,34 @@ func makeCreatedByRefJson(object runtime.Object) (string, error) { return string(createdByRefJson), nil } -func IsJobFinished(j *batch.Job) bool { +func getFinishedStatus(j *batch.Job) (bool, batch.JobConditionType) { for _, c := range j.Status.Conditions { if (c.Type == batch.JobComplete || c.Type == batch.JobFailed) && c.Status == v1.ConditionTrue { - return true + return true, c.Type } } - return false + return false, "" +} + +func IsJobFinished(j *batch.Job) bool { + isFinished, _ := getFinishedStatus(j) + return isFinished +} + +// byJobStartTime sorts a list of jobs by start timestamp, using their names as a tie breaker. +type byJobStartTime []batch.Job + +func (o byJobStartTime) Len() int { return len(o) } +func (o byJobStartTime) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o byJobStartTime) Less(i, j int) bool { + if o[j].Status.StartTime == nil { + return o[i].Status.StartTime != nil + } + + if (*o[i].Status.StartTime).Equal(*o[j].Status.StartTime) { + return o[i].Name < o[j].Name + } + + return (*o[i].Status.StartTime).Before(*o[j].Status.StartTime) } diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 6ecc8da6ff6..891c26a871c 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -16226,6 +16226,20 @@ func GetOpenAPIDefinitions(ref openapi.ReferenceCallback) map[string]openapi.Ope Ref: ref("k8s.io/kubernetes/pkg/apis/batch/v2alpha1.JobTemplateSpec"), }, }, + "successfulJobsHistoryLimit": { + SchemaProps: spec.SchemaProps{ + Description: "The number of successful finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "failedJobsHistoryLimit": { + SchemaProps: spec.SchemaProps{ + Description: "The number of failed finished jobs to retain. This is a pointer to distinguish between explicit zero and not specified.", + Type: []string{"integer"}, + Format: "int32", + }, + }, }, Required: []string{"schedule", "jobTemplate"}, }, diff --git a/test/e2e/cronjob.go b/test/e2e/cronjob.go index bcc4304c965..1b81ab5e2a9 100644 --- a/test/e2e/cronjob.go +++ b/test/e2e/cronjob.go @@ -52,6 +52,11 @@ var ( var _ = framework.KubeDescribe("CronJob", func() { f := framework.NewDefaultGroupVersionFramework("cronjob", BatchV2Alpha1GroupVersion) + sleepCommand := []string{"sleep", "300"} + + // Pod will complete instantly + successCommand := []string{"/bin/true"} + BeforeEach(func() { framework.SkipIfMissingResource(f.ClientPool, CronJobGroupVersionResource, f.Namespace.Name) }) @@ -59,7 +64,8 @@ var _ = framework.KubeDescribe("CronJob", func() { // multiple jobs running at once It("should schedule multiple jobs concurrently", func() { By("Creating a cronjob") - cronJob := newTestCronJob("concurrent", "*/1 * * * ?", batch.AllowConcurrent, true) + cronJob := newTestCronJob("concurrent", "*/1 * * * ?", batch.AllowConcurrent, + sleepCommand, nil) cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) Expect(err).NotTo(HaveOccurred()) @@ -70,7 +76,7 @@ var _ = framework.KubeDescribe("CronJob", func() { By("Ensuring at least two running jobs exists by listing jobs explicitly") jobs, err := f.ClientSet.Batch().Jobs(f.Namespace.Name).List(metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) - activeJobs := filterActiveJobs(jobs) + activeJobs, _ := filterActiveJobs(jobs) Expect(len(activeJobs) >= 2).To(BeTrue()) By("Removing cronjob") @@ -81,7 +87,8 @@ var _ = framework.KubeDescribe("CronJob", func() { // suspended should not schedule jobs It("should not schedule jobs when suspended [Slow]", func() { By("Creating a suspended cronjob") - cronJob := newTestCronJob("suspended", "*/1 * * * ?", batch.AllowConcurrent, true) + cronJob := newTestCronJob("suspended", "*/1 * * * ?", batch.AllowConcurrent, + sleepCommand, nil) cronJob.Spec.Suspend = newBool(true) cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) Expect(err).NotTo(HaveOccurred()) @@ -103,7 +110,8 @@ var _ = framework.KubeDescribe("CronJob", func() { // only single active job is allowed for ForbidConcurrent It("should not schedule new jobs when ForbidConcurrent [Slow]", func() { By("Creating a ForbidConcurrent cronjob") - cronJob := newTestCronJob("forbid", "*/1 * * * ?", batch.ForbidConcurrent, true) + cronJob := newTestCronJob("forbid", "*/1 * * * ?", batch.ForbidConcurrent, + sleepCommand, nil) cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) Expect(err).NotTo(HaveOccurred()) @@ -119,7 +127,7 @@ var _ = framework.KubeDescribe("CronJob", func() { By("Ensuring exaclty one running job exists by listing jobs explicitly") jobs, err := f.ClientSet.Batch().Jobs(f.Namespace.Name).List(metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) - activeJobs := filterActiveJobs(jobs) + activeJobs, _ := filterActiveJobs(jobs) Expect(activeJobs).To(HaveLen(1)) By("Ensuring no more jobs are scheduled") @@ -134,7 +142,8 @@ var _ = framework.KubeDescribe("CronJob", func() { // only single active job is allowed for ReplaceConcurrent It("should replace jobs when ReplaceConcurrent", func() { By("Creating a ReplaceConcurrent cronjob") - cronJob := newTestCronJob("replace", "*/1 * * * ?", batch.ReplaceConcurrent, true) + cronJob := newTestCronJob("replace", "*/1 * * * ?", batch.ReplaceConcurrent, + sleepCommand, nil) cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) Expect(err).NotTo(HaveOccurred()) @@ -150,7 +159,7 @@ var _ = framework.KubeDescribe("CronJob", func() { By("Ensuring exaclty one running job exists by listing jobs explicitly") jobs, err := f.ClientSet.Batch().Jobs(f.Namespace.Name).List(metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) - activeJobs := filterActiveJobs(jobs) + activeJobs, _ := filterActiveJobs(jobs) Expect(activeJobs).To(HaveLen(1)) By("Ensuring the job is replaced with a new one") @@ -165,7 +174,8 @@ var _ = framework.KubeDescribe("CronJob", func() { // shouldn't give us unexpected warnings It("should not emit unexpected warnings", func() { By("Creating a cronjob") - cronJob := newTestCronJob("concurrent", "*/1 * * * ?", batch.AllowConcurrent, false) + cronJob := newTestCronJob("concurrent", "*/1 * * * ?", batch.AllowConcurrent, + nil, nil) cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) Expect(err).NotTo(HaveOccurred()) @@ -187,7 +197,8 @@ var _ = framework.KubeDescribe("CronJob", func() { // deleted jobs should be removed from the active list It("should remove from active list jobs that have been deleted", func() { By("Creating a ForbidConcurrent cronjob") - cronJob := newTestCronJob("forbid", "*/1 * * * ?", batch.ForbidConcurrent, true) + cronJob := newTestCronJob("forbid", "*/1 * * * ?", batch.ForbidConcurrent, + sleepCommand, nil) cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) Expect(err).NotTo(HaveOccurred()) @@ -225,10 +236,49 @@ var _ = framework.KubeDescribe("CronJob", func() { err = deleteCronJob(f.ClientSet, f.Namespace.Name, cronJob.Name) Expect(err).NotTo(HaveOccurred()) }) + + // cleanup of successful finished jobs, with limit of one successful job + It("should delete successful finished jobs with limit of one successful job", func() { + By("Creating a AllowConcurrent cronjob with custom history limits") + successLimit := int32(1) + cronJob := newTestCronJob("concurrent-limit", "*/1 * * * ?", batch.AllowConcurrent, + successCommand, &successLimit) + cronJob, err := createCronJob(f.ClientSet, f.Namespace.Name, cronJob) + Expect(err).NotTo(HaveOccurred()) + + // Job is going to complete instantly: do not check for an active job + // as we are most likely to miss it + + By("Ensuring a finished job exists") + err = waitForAnyFinishedJob(f.ClientSet, f.Namespace.Name) + Expect(err).NotTo(HaveOccurred()) + + By("Ensuring a finished job exists by listing jobs explicitly") + jobs, err := f.ClientSet.Batch().Jobs(f.Namespace.Name).List(metav1.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + _, finishedJobs := filterActiveJobs(jobs) + Expect(len(finishedJobs) == 1).To(BeTrue()) + + // Job should get deleted when the next job finishes the next minute + By("Ensuring this job does not exist anymore") + err = waitForJobNotExist(f.ClientSet, f.Namespace.Name, finishedJobs[0]) + Expect(err).NotTo(HaveOccurred()) + + By("Ensuring there is 1 finished job by listing jobs explicitly") + jobs, err = f.ClientSet.Batch().Jobs(f.Namespace.Name).List(metav1.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + _, finishedJobs = filterActiveJobs(jobs) + Expect(len(finishedJobs) == 1).To(BeTrue()) + + By("Removing cronjob") + err = deleteCronJob(f.ClientSet, f.Namespace.Name, cronJob.Name) + Expect(err).NotTo(HaveOccurred()) + }) }) // newTestCronJob returns a cronjob which does one of several testing behaviors. -func newTestCronJob(name, schedule string, concurrencyPolicy batch.ConcurrencyPolicy, sleep bool) *batch.CronJob { +func newTestCronJob(name, schedule string, concurrencyPolicy batch.ConcurrencyPolicy, command []string, + successfulJobsHistoryLimit *int32) *batch.CronJob { parallelism := int32(1) completions := int32(1) sj := &batch.CronJob{ @@ -271,8 +321,9 @@ func newTestCronJob(name, schedule string, concurrencyPolicy batch.ConcurrencyPo }, }, } - if sleep { - sj.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = []string{"sleep", "300"} + sj.Spec.SuccessfulJobsHistoryLimit = successfulJobsHistoryLimit + if command != nil { + sj.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = command } return sj } @@ -319,6 +370,23 @@ func waitForNoJobs(c clientset.Interface, ns, jobName string, failIfNonEmpty boo }) } +// Wait for a job to not exist by listing jobs explicitly. +func waitForJobNotExist(c clientset.Interface, ns string, targetJob *batchv1.Job) error { + return wait.Poll(framework.Poll, cronJobTimeout, func() (bool, error) { + jobs, err := c.Batch().Jobs(ns).List(metav1.ListOptions{}) + if err != nil { + return false, err + } + _, finishedJobs := filterActiveJobs(jobs) + for _, job := range finishedJobs { + if targetJob.Namespace == job.Namespace && targetJob.Name == job.Name { + return false, nil + } + } + return true, nil + }) +} + // Wait for a job to be replaced with a new one. func waitForJobReplaced(c clientset.Interface, ns, previousJobName string) error { return wait.Poll(framework.Poll, cronJobTimeout, func() (bool, error) { @@ -383,11 +451,13 @@ func checkNoEventWithReason(c clientset.Interface, ns, cronJobName string, reaso return nil } -func filterActiveJobs(jobs *batchv1.JobList) (active []*batchv1.Job) { +func filterActiveJobs(jobs *batchv1.JobList) (active []*batchv1.Job, finished []*batchv1.Job) { for i := range jobs.Items { j := jobs.Items[i] if !job.IsJobFinished(&j) { active = append(active, &j) + } else { + finished = append(finished, &j) } } return