batch: add suspended job

Signed-off-by: Adhityaa Chandrasekar <adtac@google.com>
This commit is contained in:
Adhityaa Chandrasekar
2021-03-08 11:50:02 +00:00
parent 97cd5bb7e2
commit a0844da8f7
32 changed files with 818 additions and 245 deletions

View File

@@ -20,14 +20,9 @@ import (
fuzz "github.com/google/gofuzz"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/utils/pointer"
)
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}
// Funcs returns the fuzzer functions for the batch api group.
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{
@@ -48,7 +43,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
j.Parallelism = &parallelism
j.BackoffLimit = &backoffLimit
if c.Rand.Int31()%2 == 0 {
j.ManualSelector = newBool(true)
j.ManualSelector = pointer.BoolPtr(true)
} else {
j.ManualSelector = nil
}
@@ -57,6 +52,9 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
} else {
j.CompletionMode = batch.IndexedCompletion
}
// We're fuzzing the internal JobSpec type, not the v1 type, so we don't
// need to fuzz the nil value.
j.Suspend = pointer.BoolPtr(c.RandBool())
},
func(sj *batch.CronJobSpec, c fuzz.Continue) {
c.FuzzNoCustom(sj)

View File

@@ -119,8 +119,11 @@ type JobSpec struct {
// +optional
Completions *int32
// Optional 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
// Specifies the duration in seconds relative to the startTime that the job
// may be continuously active before the system tries to terminate it; value
// must be positive integer. If a Job is suspended (at creation or through an
// update), this timer will effectively be stopped and reset when the Job is
// resumed again.
// +optional
ActiveDeadlineSeconds *int64
@@ -187,19 +190,36 @@ type JobSpec struct {
// controller skips updates for the Job.
// +optional
CompletionMode CompletionMode
// Suspend specifies whether the Job controller should create Pods or not. If
// a Job is created with suspend set to true, no Pods are created by the Job
// controller. If a Job is suspended after creation (i.e. the flag goes from
// false to true), the Job controller will delete all active Pods associated
// with this Job. Users must design their workload to gracefully handle this.
// Suspending a Job will reset the StartTime field of the Job, effectively
// resetting the ActiveDeadlineSeconds timer too. This is an alpha field and
// requires the SuspendJob feature gate to be enabled; otherwise this field
// may not be set to true. Defaults to false.
// +optional
Suspend *bool
}
// JobStatus represents the current state of a Job.
type JobStatus struct {
// The latest available observations of an object's current state.
// When a job fails, one of the conditions will have type == "Failed".
// The latest available observations of an object's current state. When a Job
// fails, one of the conditions will have type "Failed" and status true. When
// a Job is suspended, one of the conditions will have type "Suspended" and
// status true; when the Job is resumed, the status of this condition will
// become false. When a Job is completed, one of the conditions will have
// type "Complete" and status true.
// +optional
Conditions []JobCondition
// Represents time when the job was acknowledged by the job controller.
// It is not guaranteed to be set in happens-before order across separate operations.
// It is represented in RFC3339 form and is in UTC.
// Represents time when the job controller started processing a job. When a
// Job is created in the suspended state, this field is not set until the
// first time it is resumed. This field is reset every time a Job is resumed
// from suspension. It is represented in RFC3339 form and is in UTC.
// +optional
StartTime *metav1.Time
@@ -238,6 +258,8 @@ type JobConditionType string
// These are valid conditions of a job.
const (
// JobSuspended means the job has been suspended.
JobSuspended JobConditionType = "Suspended"
// JobComplete means the job has completed its execution.
JobComplete JobConditionType = "Complete"
// JobFailed means the job has failed its execution.
@@ -246,7 +268,7 @@ const (
// JobCondition describes current state of a job.
type JobCondition struct {
// Type of job condition, Complete or Failed.
// Type of job condition.
Type JobConditionType
// Status of the condition, one of True, False, Unknown.
Status api.ConditionStatus
@@ -319,7 +341,7 @@ type CronJobSpec struct {
ConcurrencyPolicy ConcurrencyPolicy
// This flag tells the controller to suspend subsequent executions, it does
// not apply to already started executions. Defaults to false.
// not apply to already started executions. Defaults to false.
// +optional
Suspend *bool

View File

@@ -46,6 +46,9 @@ func SetDefaults_Job(obj *batchv1.Job) {
if len(obj.Spec.CompletionMode) == 0 {
obj.Spec.CompletionMode = batchv1.NonIndexedCompletion
}
if obj.Spec.Suspend == nil {
obj.Spec.Suspend = utilpointer.BoolPtr(false)
}
}
func SetDefaults_CronJob(obj *batchv1.CronJob) {

View File

@@ -20,6 +20,7 @@ import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -27,7 +28,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/core/install"
utilpointer "k8s.io/utils/pointer"
"k8s.io/utils/pointer"
. "k8s.io/kubernetes/pkg/apis/batch/v1"
)
@@ -49,15 +50,36 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
},
},
expectLabels: true,
},
"All unspecified -> all integers are defaulted and no default labels": {
"suspend set, everything else is defaulted": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Suspend: pointer.BoolPtr(true),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
},
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(true),
},
},
expectLabels: true,
},
"All unspecified -> all pointers, CompletionMode are defaulted and no default labels": {
original: &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"mylabel": "myvalue"},
@@ -70,17 +92,18 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
},
},
},
"WQ: Parallelism explicitly 0 and completions unset -> BackoffLimit is defaulted": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: utilpointer.Int32Ptr(0),
Parallelism: pointer.Int32Ptr(0),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -88,9 +111,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: utilpointer.Int32Ptr(0),
BackoffLimit: utilpointer.Int32Ptr(6),
Parallelism: pointer.Int32Ptr(0),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
},
},
expectLabels: true,
@@ -98,7 +122,7 @@ func TestSetDefaultJob(t *testing.T) {
"WQ: Parallelism explicitly 2 and completions unset -> BackoffLimit is defaulted": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: utilpointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(2),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -106,9 +130,10 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Parallelism: utilpointer.Int32Ptr(2),
BackoffLimit: utilpointer.Int32Ptr(6),
Parallelism: pointer.Int32Ptr(2),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
},
},
expectLabels: true,
@@ -116,7 +141,7 @@ func TestSetDefaultJob(t *testing.T) {
"Completions explicitly 2 and others unset -> parallelism and BackoffLimit are defaulted": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(2),
Completions: pointer.Int32Ptr(2),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -124,10 +149,11 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(2),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(6),
Completions: pointer.Int32Ptr(2),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(6),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
},
},
expectLabels: true,
@@ -135,7 +161,7 @@ func TestSetDefaultJob(t *testing.T) {
"BackoffLimit explicitly 5 and others unset -> parallelism and completions are defaulted": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
BackoffLimit: utilpointer.Int32Ptr(5),
BackoffLimit: pointer.Int32Ptr(5),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -143,10 +169,11 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(1),
Parallelism: utilpointer.Int32Ptr(1),
BackoffLimit: utilpointer.Int32Ptr(5),
Completions: pointer.Int32Ptr(1),
Parallelism: pointer.Int32Ptr(1),
BackoffLimit: pointer.Int32Ptr(5),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(false),
},
},
expectLabels: true,
@@ -154,10 +181,11 @@ func TestSetDefaultJob(t *testing.T) {
"All set -> no change": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(8),
Parallelism: utilpointer.Int32Ptr(9),
BackoffLimit: utilpointer.Int32Ptr(10),
Completions: pointer.Int32Ptr(8),
Parallelism: pointer.Int32Ptr(9),
BackoffLimit: pointer.Int32Ptr(10),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(true),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -165,10 +193,11 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(8),
Parallelism: utilpointer.Int32Ptr(9),
BackoffLimit: utilpointer.Int32Ptr(10),
Completions: pointer.Int32Ptr(8),
Parallelism: pointer.Int32Ptr(9),
BackoffLimit: pointer.Int32Ptr(10),
CompletionMode: batchv1.NonIndexedCompletion,
Suspend: pointer.BoolPtr(true),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -179,10 +208,11 @@ func TestSetDefaultJob(t *testing.T) {
"All set, flipped -> no change": {
original: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(11),
Parallelism: utilpointer.Int32Ptr(10),
BackoffLimit: utilpointer.Int32Ptr(9),
Completions: pointer.Int32Ptr(11),
Parallelism: pointer.Int32Ptr(10),
BackoffLimit: pointer.Int32Ptr(9),
CompletionMode: batchv1.IndexedCompletion,
Suspend: pointer.BoolPtr(true),
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: defaultLabels},
},
@@ -190,10 +220,11 @@ func TestSetDefaultJob(t *testing.T) {
},
expected: &batchv1.Job{
Spec: batchv1.JobSpec{
Completions: utilpointer.Int32Ptr(11),
Parallelism: utilpointer.Int32Ptr(10),
BackoffLimit: utilpointer.Int32Ptr(9),
Completions: pointer.Int32Ptr(11),
Parallelism: pointer.Int32Ptr(10),
BackoffLimit: pointer.Int32Ptr(9),
CompletionMode: batchv1.IndexedCompletion,
Suspend: pointer.BoolPtr(true),
},
},
expectLabels: true,
@@ -211,6 +242,9 @@ func TestSetDefaultJob(t *testing.T) {
t.Fatalf("Unexpected object: %v", actual)
}
if diff := cmp.Diff(expected.Spec.Suspend, actual.Spec.Suspend); diff != "" {
t.Errorf(".spec.suspend does not match; -want,+got:\n%s", diff)
}
validateDefaultInt32(t, "Completions", actual.Spec.Completions, expected.Spec.Completions)
validateDefaultInt32(t, "Parallelism", actual.Spec.Parallelism, expected.Spec.Parallelism)
validateDefaultInt32(t, "BackoffLimit", actual.Spec.BackoffLimit, expected.Spec.BackoffLimit)
@@ -271,8 +305,8 @@ func TestSetDefaultCronJob(t *testing.T) {
Spec: batchv1.CronJobSpec{
ConcurrencyPolicy: batchv1.AllowConcurrent,
Suspend: newBool(false),
SuccessfulJobsHistoryLimit: utilpointer.Int32Ptr(3),
FailedJobsHistoryLimit: utilpointer.Int32Ptr(1),
SuccessfulJobsHistoryLimit: pointer.Int32Ptr(3),
FailedJobsHistoryLimit: pointer.Int32Ptr(1),
},
},
},
@@ -281,16 +315,16 @@ func TestSetDefaultCronJob(t *testing.T) {
Spec: batchv1.CronJobSpec{
ConcurrencyPolicy: batchv1.ForbidConcurrent,
Suspend: newBool(true),
SuccessfulJobsHistoryLimit: utilpointer.Int32Ptr(5),
FailedJobsHistoryLimit: utilpointer.Int32Ptr(5),
SuccessfulJobsHistoryLimit: pointer.Int32Ptr(5),
FailedJobsHistoryLimit: pointer.Int32Ptr(5),
},
},
expected: &batchv1.CronJob{
Spec: batchv1.CronJobSpec{
ConcurrencyPolicy: batchv1.ForbidConcurrent,
Suspend: newBool(true),
SuccessfulJobsHistoryLimit: utilpointer.Int32Ptr(5),
FailedJobsHistoryLimit: utilpointer.Int32Ptr(5),
SuccessfulJobsHistoryLimit: pointer.Int32Ptr(5),
FailedJobsHistoryLimit: pointer.Int32Ptr(5),
},
},
},

View File

@@ -393,6 +393,7 @@ func autoConvert_v1_JobSpec_To_batch_JobSpec(in *v1.JobSpec, out *batch.JobSpec,
}
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
out.CompletionMode = batch.CompletionMode(in.CompletionMode)
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
return nil
}
@@ -408,6 +409,7 @@ func autoConvert_batch_JobSpec_To_v1_JobSpec(in *batch.JobSpec, out *v1.JobSpec,
}
out.TTLSecondsAfterFinished = (*int32)(unsafe.Pointer(in.TTLSecondsAfterFinished))
out.CompletionMode = v1.CompletionMode(in.CompletionMode)
out.Suspend = (*bool)(unsafe.Pointer(in.Suspend))
return nil
}

View File

@@ -271,6 +271,11 @@ func (in *JobSpec) DeepCopyInto(out *JobSpec) {
*out = new(int32)
**out = **in
}
if in.Suspend != nil {
in, out := &in.Suspend, &out.Suspend
*out = new(bool)
**out = **in
}
return
}