mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 09:52:49 +00:00
Graduate IndexedJob to stable
- Lock feature gate to true and schedule for deletion in 1.26 - Remove checks on feature gate - Graduate E2E test to Conformance Change-Id: I6814819d318edaed5c86dae4055f4b050a4d39fd
This commit is contained in:
parent
1a5abe5d1f
commit
2c5d0a273c
2
api/openapi-spec/swagger.json
generated
2
api/openapi-spec/swagger.json
generated
@ -4226,7 +4226,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"completionMode": {
|
||||
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nThis field is beta-level. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
|
||||
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nMore completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, which is possible during upgrades due to version skew, the controller skips updates for the Job.",
|
||||
"type": "string"
|
||||
},
|
||||
"completions": {
|
||||
|
@ -275,7 +275,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"completionMode": {
|
||||
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nThis field is beta-level. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
|
||||
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nMore completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, which is possible during upgrades due to version skew, the controller skips updates for the Job.",
|
||||
"type": "string"
|
||||
},
|
||||
"completions": {
|
||||
|
@ -15,7 +15,7 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"completionMode": {
|
||||
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nThis field is beta-level. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
|
||||
"description": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nMore completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, which is possible during upgrades due to version skew, the controller skips updates for the Job.",
|
||||
"type": "string"
|
||||
},
|
||||
"completions": {
|
||||
|
@ -194,9 +194,10 @@ type JobSpec struct {
|
||||
// `$(job-name)-$(index)-$(random-string)`,
|
||||
// the Pod hostname takes the form `$(job-name)-$(index)`.
|
||||
//
|
||||
// This field is beta-level. More completion modes can be added in the future.
|
||||
// If the Job controller observes a mode that it doesn't recognize, the
|
||||
// controller skips updates for the Job.
|
||||
// More completion modes can be added in the future.
|
||||
// If the Job controller observes a mode that it doesn't recognize, which
|
||||
// is possible during upgrades due to version skew, the controller
|
||||
// skips updates for the Job.
|
||||
// +optional
|
||||
CompletionMode *CompletionMode
|
||||
|
||||
|
@ -19,8 +19,6 @@ package v1
|
||||
import (
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
@ -45,7 +43,7 @@ func SetDefaults_Job(obj *batchv1.Job) {
|
||||
if labels != nil && len(obj.Labels) == 0 {
|
||||
obj.Labels = labels
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && obj.Spec.CompletionMode == nil {
|
||||
if obj.Spec.CompletionMode == nil {
|
||||
mode := batchv1.NonIndexedCompletion
|
||||
obj.Spec.CompletionMode = &mode
|
||||
}
|
||||
|
@ -25,12 +25,9 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
_ "k8s.io/kubernetes/pkg/apis/batch/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
. "k8s.io/kubernetes/pkg/apis/batch/v1"
|
||||
@ -39,31 +36,11 @@ import (
|
||||
func TestSetDefaultJob(t *testing.T) {
|
||||
defaultLabels := map[string]string{"default": "default"}
|
||||
tests := map[string]struct {
|
||||
indexedJobEnabled bool
|
||||
original *batchv1.Job
|
||||
expected *batchv1.Job
|
||||
expectLabels bool
|
||||
original *batchv1.Job
|
||||
expected *batchv1.Job
|
||||
expectLabels bool
|
||||
}{
|
||||
"All unspecified -> sets all to default values": {
|
||||
original: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
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),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
},
|
||||
"All unspecified, indexed job enabled -> sets all to default values": {
|
||||
indexedJobEnabled: true,
|
||||
original: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
@ -92,10 +69,11 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
@ -111,10 +89,11 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
Suspend: pointer.BoolPtr(true),
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(true),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
@ -132,10 +111,11 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -150,9 +130,10 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Parallelism: pointer.Int32Ptr(0),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
Parallelism: pointer.Int32Ptr(0),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
@ -168,9 +149,10 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Parallelism: pointer.Int32Ptr(2),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
Parallelism: pointer.Int32Ptr(2),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
@ -186,10 +168,11 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: pointer.Int32Ptr(2),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
Completions: pointer.Int32Ptr(2),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(6),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
@ -205,10 +188,11 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
},
|
||||
expected: &batchv1.Job{
|
||||
Spec: batchv1.JobSpec{
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(5),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
Completions: pointer.Int32Ptr(1),
|
||||
Parallelism: pointer.Int32Ptr(1),
|
||||
BackoffLimit: pointer.Int32Ptr(5),
|
||||
CompletionMode: completionModePtr(batchv1.NonIndexedCompletion),
|
||||
Suspend: pointer.BoolPtr(false),
|
||||
},
|
||||
},
|
||||
expectLabels: true,
|
||||
@ -268,8 +252,6 @@ func TestSetDefaultJob(t *testing.T) {
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, test.indexedJobEnabled)()
|
||||
|
||||
original := test.original
|
||||
expected := test.expected
|
||||
obj2 := roundTrip(t, runtime.Object(original))
|
||||
|
@ -634,11 +634,6 @@ func (jm *Controller) syncJob(ctx context.Context, key string) (forget bool, rEr
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Cannot create Pods if this is an Indexed Job and the feature is disabled.
|
||||
if !feature.DefaultFeatureGate.Enabled(features.IndexedJob) && isIndexedJob(&job) {
|
||||
jm.recorder.Event(&job, v1.EventTypeWarning, "IndexedJobDisabled", "Skipped Indexed Job sync because feature is disabled.")
|
||||
return false, nil
|
||||
}
|
||||
if job.Spec.CompletionMode != nil && *job.Spec.CompletionMode != batch.NonIndexedCompletion && *job.Spec.CompletionMode != batch.IndexedCompletion {
|
||||
jm.recorder.Event(&job, v1.EventTypeWarning, "UnknownCompletionMode", "Skipped Job sync because completion mode is unknown")
|
||||
return false, nil
|
||||
|
@ -224,7 +224,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedPodPatches int
|
||||
|
||||
// features
|
||||
indexedJobEnabled bool
|
||||
jobReadyPodsEnabled bool
|
||||
}{
|
||||
"job start": {
|
||||
@ -490,7 +489,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedCreations: 2,
|
||||
expectedActive: 2,
|
||||
expectedCreatedIndexes: sets.NewInt(0, 1),
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job completed": {
|
||||
parallelism: 2,
|
||||
@ -510,7 +508,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedCondition: &jobConditionComplete,
|
||||
expectedConditionStatus: v1.ConditionTrue,
|
||||
expectedPodPatches: 4,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job repeated completed index": {
|
||||
parallelism: 2,
|
||||
@ -529,7 +526,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedCompletedIdxs: "0,1",
|
||||
expectedCreatedIndexes: sets.NewInt(2),
|
||||
expectedPodPatches: 3,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job some running and completed pods": {
|
||||
parallelism: 8,
|
||||
@ -553,7 +549,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedCompletedIdxs: "2,4,5,7-9",
|
||||
expectedCreatedIndexes: sets.NewInt(1, 6, 10, 11, 12, 13),
|
||||
expectedPodPatches: 6,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job some failed pods": {
|
||||
parallelism: 3,
|
||||
@ -570,7 +565,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedFailed: 2,
|
||||
expectedCreatedIndexes: sets.NewInt(0, 2),
|
||||
expectedPodPatches: 2,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job some pods without index": {
|
||||
parallelism: 2,
|
||||
@ -596,7 +590,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedFailed: 0,
|
||||
expectedCompletedIdxs: "0",
|
||||
expectedPodPatches: 8,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job repeated indexes": {
|
||||
parallelism: 5,
|
||||
@ -619,7 +612,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedSucceeded: 1,
|
||||
expectedCompletedIdxs: "0",
|
||||
expectedPodPatches: 5,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job with indexes outside of range": {
|
||||
parallelism: 2,
|
||||
@ -641,19 +633,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
expectedActive: 0,
|
||||
expectedFailed: 0,
|
||||
expectedPodPatches: 5,
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"indexed job feature disabled": {
|
||||
parallelism: 2,
|
||||
completions: 3,
|
||||
backoffLimit: 6,
|
||||
completionMode: batch.IndexedCompletion,
|
||||
podsWithIndexes: []indexPhase{
|
||||
{"0", v1.PodRunning},
|
||||
{"1", v1.PodSucceeded},
|
||||
},
|
||||
// No status updates.
|
||||
indexedJobEnabled: false,
|
||||
},
|
||||
"suspending a job with satisfied expectations": {
|
||||
// Suspended Job should delete active pods when expectations are
|
||||
@ -727,7 +706,6 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
if wFinalizers && tc.podControllerError != nil {
|
||||
t.Skip("Can't track status if finalizers can't be removed")
|
||||
}
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, tc.indexedJobEnabled)()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobReadyPods, tc.jobReadyPodsEnabled)()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, wFinalizers)()
|
||||
|
||||
@ -857,7 +835,7 @@ func TestControllerSyncJob(t *testing.T) {
|
||||
if actual.Status.StartTime != nil && tc.suspend {
|
||||
t.Error("Unexpected .status.startTime not nil when suspend is true")
|
||||
}
|
||||
if actual.Status.StartTime == nil && tc.indexedJobEnabled && !tc.suspend {
|
||||
if actual.Status.StartTime == nil && !tc.suspend {
|
||||
t.Error("Missing .status.startTime")
|
||||
}
|
||||
// validate conditions
|
||||
|
@ -214,6 +214,7 @@ const (
|
||||
// owner: @alculquicondor
|
||||
// alpha: v1.21
|
||||
// beta: v1.22
|
||||
// stable: v1.24
|
||||
//
|
||||
// Allows Job controller to manage Pod completions per completion index.
|
||||
IndexedJob featuregate.Feature = "IndexedJob"
|
||||
@ -887,7 +888,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
NetworkPolicyEndPort: {Default: true, PreRelease: featuregate.Beta},
|
||||
ProcMountType: {Default: false, PreRelease: featuregate.Alpha},
|
||||
TTLAfterFinished: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
|
||||
IndexedJob: {Default: true, PreRelease: featuregate.Beta},
|
||||
IndexedJob: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26
|
||||
JobTrackingWithFinalizers: {Default: true, PreRelease: featuregate.Beta},
|
||||
JobReadyPods: {Default: false, PreRelease: featuregate.Alpha},
|
||||
KubeletPodResources: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
2
pkg/generated/openapi/zz_generated.openapi.go
generated
2
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -12975,7 +12975,7 @@ func schema_k8sio_api_batch_v1_JobSpec(ref common.ReferenceCallback) common.Open
|
||||
},
|
||||
"completionMode": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nThis field is beta-level. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
|
||||
Description: "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nMore completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, which is possible during upgrades due to version skew, the controller skips updates for the Job.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
|
@ -93,10 +93,6 @@ func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
|
||||
job.Generation = 1
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) {
|
||||
job.Spec.CompletionMode = nil
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.JobTrackingWithFinalizers) {
|
||||
// Until this feature graduates to GA and soaks in clusters, we use an
|
||||
// annotation to mark whether jobs are tracked with it.
|
||||
@ -133,10 +129,6 @@ func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
|
||||
oldJob := old.(*batch.Job)
|
||||
newJob.Status = oldJob.Status
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob) && oldJob.Spec.CompletionMode == nil {
|
||||
newJob.Spec.CompletionMode = nil
|
||||
}
|
||||
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.JobTrackingWithFinalizers) && !hasJobTrackingAnnotation(oldJob) {
|
||||
dropJobTrackingAnnotation(newJob)
|
||||
}
|
||||
|
@ -42,20 +42,15 @@ var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Deta
|
||||
|
||||
func TestJobStrategy(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
indexedJobEnabled bool
|
||||
trackingWithFinalizersEnabled bool
|
||||
}{
|
||||
"features disabled": {},
|
||||
"indexed job enabled": {
|
||||
indexedJobEnabled: true,
|
||||
},
|
||||
"new job tracking enabled": {
|
||||
trackingWithFinalizersEnabled: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IndexedJob, tc.indexedJobEnabled)()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.JobTrackingWithFinalizers, tc.trackingWithFinalizersEnabled)()
|
||||
testJobStrategy(t)
|
||||
})
|
||||
@ -63,7 +58,6 @@ func TestJobStrategy(t *testing.T) {
|
||||
}
|
||||
|
||||
func testJobStrategy(t *testing.T) {
|
||||
indexedJobEnabled := utilfeature.DefaultFeatureGate.Enabled(features.IndexedJob)
|
||||
trackingWithFinalizersEnabled := utilfeature.DefaultFeatureGate.Enabled(features.JobTrackingWithFinalizers)
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
if !Strategy.NamespaceScoped() {
|
||||
@ -121,8 +115,8 @@ func testJobStrategy(t *testing.T) {
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Unexpected error validating %v", errs)
|
||||
}
|
||||
if indexedJobEnabled != (job.Spec.CompletionMode != nil) {
|
||||
t.Errorf("Job should allow setting .spec.completionMode only when %v feature is enabled", features.IndexedJob)
|
||||
if job.Spec.CompletionMode == nil {
|
||||
t.Errorf("Job should allow setting .spec.completionMode")
|
||||
}
|
||||
wantAnnotations := map[string]string{"foo": "bar"}
|
||||
if trackingWithFinalizersEnabled {
|
||||
|
@ -248,9 +248,10 @@ message JobSpec {
|
||||
// `$(job-name)-$(index)-$(random-string)`,
|
||||
// the Pod hostname takes the form `$(job-name)-$(index)`.
|
||||
//
|
||||
// This field is beta-level. More completion modes can be added in the future.
|
||||
// If the Job controller observes a mode that it doesn't recognize, the
|
||||
// controller skips updates for the Job.
|
||||
// More completion modes can be added in the future.
|
||||
// If the Job controller observes a mode that it doesn't recognize, which
|
||||
// is possible during upgrades due to version skew, the controller
|
||||
// skips updates for the Job.
|
||||
// +optional
|
||||
optional string completionMode = 9;
|
||||
|
||||
|
@ -176,9 +176,10 @@ type JobSpec struct {
|
||||
// `$(job-name)-$(index)-$(random-string)`,
|
||||
// the Pod hostname takes the form `$(job-name)-$(index)`.
|
||||
//
|
||||
// This field is beta-level. More completion modes can be added in the future.
|
||||
// If the Job controller observes a mode that it doesn't recognize, the
|
||||
// controller skips updates for the Job.
|
||||
// More completion modes can be added in the future.
|
||||
// If the Job controller observes a mode that it doesn't recognize, which
|
||||
// is possible during upgrades due to version skew, the controller
|
||||
// skips updates for the Job.
|
||||
// +optional
|
||||
CompletionMode *CompletionMode `json:"completionMode,omitempty" protobuf:"bytes,9,opt,name=completionMode,casttype=CompletionMode"`
|
||||
|
||||
|
@ -119,7 +119,7 @@ var map_JobSpec = map[string]string{
|
||||
"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.",
|
||||
"completionMode": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nThis field is beta-level. More completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, the controller skips updates for the Job.",
|
||||
"completionMode": "CompletionMode specifies how Pod completions are tracked. It can be `NonIndexed` (default) or `Indexed`.\n\n`NonIndexed` means that the Job is considered complete when there have been .spec.completions successfully completed Pods. Each Pod completion is homologous to each other.\n\n`Indexed` means that the Pods of a Job get an associated completion index from 0 to (.spec.completions - 1), available in the annotation batch.kubernetes.io/job-completion-index. The Job is considered complete when there is one successfully completed Pod for each index. When value is `Indexed`, .spec.completions must be specified and `.spec.parallelism` must be less than or equal to 10^5. In addition, The Pod name takes the form `$(job-name)-$(index)-$(random-string)`, the Pod hostname takes the form `$(job-name)-$(index)`.\n\nMore completion modes can be added in the future. If the Job controller observes a mode that it doesn't recognize, which is possible during upgrades due to version skew, the controller skips updates for the Job.",
|
||||
"suspend": "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. Defaults to false.",
|
||||
}
|
||||
|
||||
|
7
test/conformance/testdata/conformance.yaml
vendored
7
test/conformance/testdata/conformance.yaml
vendored
@ -763,6 +763,13 @@
|
||||
pod. Modify the labels of one of the Job's Pods. The Job MUST release the Pod.
|
||||
release: v1.16
|
||||
file: test/e2e/apps/job.go
|
||||
- testname: Ensure Pods of an Indexed Job get a unique index.
|
||||
codename: '[sig-apps] Job should create pods for an Indexed job with completion
|
||||
indexes and specified hostname [Conformance]'
|
||||
description: Create an Indexed job. Job MUST complete successfully. Ensure that
|
||||
created pods have completion index annotation and environment variable.
|
||||
release: v1.24
|
||||
file: test/e2e/apps/job.go
|
||||
- testname: Jobs, active pods, graceful termination
|
||||
codename: '[sig-apps] Job should delete a job [Conformance]'
|
||||
description: Create a job. Ensure the active pods reflect paralellism in the namespace
|
||||
|
@ -144,10 +144,12 @@ var _ = SIGDescribe("Job", func() {
|
||||
})
|
||||
|
||||
/*
|
||||
Testcase: Ensure Pods of an Indexed Job get a unique index.
|
||||
Description: Create an Indexed Job, wait for completion, capture the output of the pods and verify that they contain the completion index.
|
||||
Release: v1.24
|
||||
Testname: Ensure Pods of an Indexed Job get a unique index.
|
||||
Description: Create an Indexed job. Job MUST complete successfully.
|
||||
Ensure that created pods have completion index annotation and environment variable.
|
||||
*/
|
||||
ginkgo.It("should create pods for an Indexed job with completion indexes and specified hostname", func() {
|
||||
framework.ConformanceIt("should create pods for an Indexed job with completion indexes and specified hostname", func() {
|
||||
ginkgo.By("Creating Indexed job")
|
||||
job := e2ejob.NewTestJob("succeed", "indexed-job", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
|
||||
mode := batchv1.IndexedCompletion
|
||||
|
@ -373,7 +373,6 @@ func TestIndexedJob(t *testing.T) {
|
||||
for _, wFinalizers := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("finalizers=%t", wFinalizers), func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.JobTrackingWithFinalizers, wFinalizers)()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, true)()
|
||||
|
||||
closeFn, restConfig, clientSet, ns := setup(t, "indexed")
|
||||
defer closeFn()
|
||||
@ -411,30 +410,10 @@ func TestIndexedJob(t *testing.T) {
|
||||
}, wFinalizers)
|
||||
validateIndexedJobPods(ctx, t, clientSet, jobObj, sets.NewInt(0, 2, 3), "1")
|
||||
|
||||
// Disable feature gate and restart controller.
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, false)()
|
||||
cancel()
|
||||
ctx, cancel = startJobController(restConfig, clientSet)
|
||||
events, err := clientSet.EventsV1().Events(ns.Name).Watch(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer events.Stop()
|
||||
|
||||
// One Pod fails, but no recreations happen because feature is disabled.
|
||||
// One Pod fails, which should be recreated.
|
||||
if err := setJobPhaseForIndex(ctx, clientSet, jobObj, v1.PodFailed, 2); err != nil {
|
||||
t.Fatal("Failed trying to succeed pod with index 2")
|
||||
}
|
||||
if err := waitForEvent(events, jobObj.UID, "IndexedJobDisabled"); err != nil {
|
||||
t.Errorf("Waiting for an event for IndexedJobDisabled: %v", err)
|
||||
}
|
||||
validateIndexedJobPods(ctx, t, clientSet, jobObj, sets.NewInt(0, 3), "1")
|
||||
|
||||
// Re-enable feature gate and restart controller. Failed Pod should be recreated now.
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.IndexedJob, true)()
|
||||
cancel()
|
||||
ctx, cancel = startJobController(restConfig, clientSet)
|
||||
|
||||
validateJobPodsStatus(ctx, t, clientSet, jobObj, podsByStatus{
|
||||
Active: 3,
|
||||
Failed: 1,
|
||||
|
Loading…
Reference in New Issue
Block a user