Merge pull request #21776 from erictune/job-sel-gen

Selector generation for batch/v1 Job
This commit is contained in:
Fabio Yeon 2016-02-26 11:58:21 -08:00
commit 3dab166da4
38 changed files with 1078 additions and 324 deletions

View File

@ -991,7 +991,7 @@
"completions": { "completions": {
"type": "integer", "type": "integer",
"format": "int32", "format": "int32",
"description": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job." "description": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md"
}, },
"activeDeadlineSeconds": { "activeDeadlineSeconds": {
"type": "integer", "type": "integer",
@ -1000,7 +1000,11 @@
}, },
"selector": { "selector": {
"$ref": "v1.LabelSelector", "$ref": "v1.LabelSelector",
"description": "Selector is a label query over pods that should match the pod count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors" "description": "Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
},
"manualSelector": {
"type": "boolean",
"description": "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: http://releases.k8s.io/HEAD/docs/design/selector-generation.md"
}, },
"template": { "template": {
"$ref": "v1.PodTemplateSpec", "$ref": "v1.PodTemplateSpec",

View File

@ -7326,7 +7326,7 @@
"completions": { "completions": {
"type": "integer", "type": "integer",
"format": "int32", "format": "int32",
"description": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job." "description": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md"
}, },
"activeDeadlineSeconds": { "activeDeadlineSeconds": {
"type": "integer", "type": "integer",
@ -7335,7 +7335,11 @@
}, },
"selector": { "selector": {
"$ref": "v1beta1.LabelSelector", "$ref": "v1beta1.LabelSelector",
"description": "Selector is a label query over pods that should match the pod count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors" "description": "Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
},
"autoSelector": {
"type": "boolean",
"description": "AutoSelector controls generation of pod labels and pod selectors. It was not present in the original extensions/v1beta1 Job definition, but exists to allow conversion from batch/v1 Jobs, where it corresponds to, but has the opposite meaning as, ManualSelector. More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md"
}, },
"template": { "template": {
"$ref": "v1.PodTemplateSpec", "$ref": "v1.PodTemplateSpec",

View File

@ -4437,7 +4437,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">completions</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">completions</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job.</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/jobs.md">http://releases.k8s.io/HEAD/docs/user-guide/jobs.md</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
@ -4451,12 +4451,19 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">selector</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">selector</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Selector is a label query over pods that should match the pod count. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors">http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors">http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1beta1_labelselector">v1beta1.LabelSelector</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1beta1_labelselector">v1beta1.LabelSelector</a></p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">autoSelector</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">AutoSelector controls generation of pod labels and pod selectors. It was not present in the original extensions/v1beta1 Job definition, but exists to allow conversion from batch/v1 Jobs, where it corresponds to, but has the opposite meaning as, ManualSelector. More info: <a href="http://releases.k8s.io/HEAD/docs/design/selector-generation.md">http://releases.k8s.io/HEAD/docs/design/selector-generation.md</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">template</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">template</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Template is the object that describes the pod that will be created when executing a job. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/jobs.md">http://releases.k8s.io/HEAD/docs/user-guide/jobs.md</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">Template is the object that describes the pod that will be created when executing a job. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/jobs.md">http://releases.k8s.io/HEAD/docs/user-guide/jobs.md</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>

View File

@ -1,16 +1,11 @@
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: pi name: pi
spec: spec:
selector:
matchLabels:
app: pi
template: template:
metadata: metadata:
name: pi name: pi
labels:
app: pi
spec: spec:
containers: containers:
- name: pi - name: pi

View File

@ -47,6 +47,8 @@ Documentation for other releases can be found at
- [Controlling Parallelism](#controlling-parallelism) - [Controlling Parallelism](#controlling-parallelism)
- [Handling Pod and Container Failures](#handling-pod-and-container-failures) - [Handling Pod and Container Failures](#handling-pod-and-container-failures)
- [Job Patterns](#job-patterns) - [Job Patterns](#job-patterns)
- [Advanced Usage](#advanced-usage)
- [Specifying your own pod selector](#specifying-your-own-pod-selector)
- [Alternatives](#alternatives) - [Alternatives](#alternatives)
- [Bare Pods](#bare-pods) - [Bare Pods](#bare-pods)
- [Replication Controller](#replication-controller) - [Replication Controller](#replication-controller)
@ -76,19 +78,14 @@ It takes around 10s to complete.
<!-- BEGIN MUNGE: EXAMPLE job.yaml --> <!-- BEGIN MUNGE: EXAMPLE job.yaml -->
```yaml ```yaml
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: pi name: pi
spec: spec:
selector:
matchLabels:
app: pi
template: template:
metadata: metadata:
name: pi name: pi
labels:
app: pi
spec: spec:
containers: containers:
- name: pi - name: pi
@ -170,21 +167,9 @@ Only a [`RestartPolicy`](pod-states.md) equal to `Never` or `OnFailure` are allo
### Pod Selector ### Pod Selector
The `.spec.selector` field is a label query over a set of pods. The `.spec.selector` field is optional. In almost all cases you should not specify it.
See section [specifying your own pod selector](#specifying-your-own-pod-selector).
The `spec.selector` is an object consisting of two fields:
* `matchLabels` - works the same as the `.spec.selector` of a [ReplicationController](replication-controller.md)
* `matchExpressions` - allows to build more sophisticated selectors by specifying key,
list of values and an operator that relates the key and values.
When the two are specified the result is ANDed.
If `.spec.selector` is unspecified, `.spec.selector.matchLabels` will be defaulted to
`.spec.template.metadata.labels`.
Also you should not normally create any pods whose labels match this selector, either directly,
via another Job, or via another controller such as ReplicationController. Otherwise, the Job will
think that those pods were created by it. Kubernetes will not stop you from doing this.
### Parallel Jobs ### Parallel Jobs
@ -323,6 +308,70 @@ Here, `W` is the number of work items.
| Single Job with Static Work Assignment | W | any | | Single Job with Static Work Assignment | W | any |
## Advanced Usage
### Specifying your own pod selector
Normally, when you create a job object, you do not specify `spec.selector`.
The system defaulting logic adds this field when the job is created.
It picks a selector value that will not overlap with any other jobs.
However, in some cases, you might need to override this automatically set selector.
To do this, you can specify the `spec.selector` of the job.
Be very careful when doing this. If you specify a label selector which is not
unique to the pods of that job, and which matches unrelated pods, then pods of the unrelated
job may be deleted, or this job may count other pods as completing it, or one or both
of the jobs may refuse to create pods or run to completion. If a non-unique selector is
chosen, then other controllers (e.g. ReplicationController) and their pods may behave
in unpredicatable ways too. Kubernetes will not stop you from making a mistake when
specifying `spec.selector`.
Here is an example of a case when you might want to use this feature.
Say job `old` is already running. You want existing pods
to keep running, but you want the rest of the pods it creates
to use a different pod template and for the job to have a new name.
You cannot update the job because these fields are not updatable.
Therefore, you delete job `old` but leave its pods
running, using `kubectl delete jobs/old-one --cascade=false`.
Before deleting it, you make a note of what selector it uses:
```
kind: Job
metadata:
name: old
...
spec:
selector:
matchLabels:
job-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
...
```
Then you create a new job with name `new` and you explicitly specify the same selector.
Since the existing pods have label `job-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002`,
they are controlled by job `new` as well.
You need to specify `manualSelector: true` in the new job since you are not using
the selector that the system normally generates for you automatically.
```
kind: Job
metadata:
name: new
...
spec:
manualSelector: true
selector:
matchLabels:
job-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
...
```
The new Job itself will have a different uid from `a8f3d00d-c6d2-11e5-9f87-42010af00002`. Setting
`manualSelector: true` tells the system to that you know what you are doing and to allow this
mismatch.
## Alternatives ## Alternatives

View File

@ -32,7 +32,9 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
expvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" expvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/registry/job"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/validation/field" "k8s.io/kubernetes/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/util/yaml" "k8s.io/kubernetes/pkg/util/yaml"
schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api" schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api"
@ -111,7 +113,10 @@ func validateObject(obj runtime.Object) (errors field.ErrorList) {
if t.Namespace == "" { if t.Namespace == "" {
t.Namespace = api.NamespaceDefault t.Namespace = api.NamespaceDefault
} }
errors = expvalidation.ValidateJob(t) // Job needs generateSelector called before validation, and job.Validate does this.
// See: https://github.com/kubernetes/kubernetes/issues/20951#issuecomment-187787040
t.ObjectMeta.UID = types.UID("fakeuid")
errors = job.Strategy.Validate(nil, t)
case *extensions.Ingress: case *extensions.Ingress:
if t.Namespace == "" { if t.Namespace == "" {
t.Namespace = api.NamespaceDefault t.Namespace = api.NamespaceDefault

View File

@ -40,21 +40,18 @@ First, create a template of a Job object:
<!-- BEGIN MUNGE: EXAMPLE job.yaml.txt --> <!-- BEGIN MUNGE: EXAMPLE job.yaml.txt -->
``` ```
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: process-item-$ITEM name: process-item-$ITEM
labels:
jobgroup: jobexample
spec: spec:
selector:
matchLabels:
app: jobexample
item: $ITEM
template: template:
metadata: metadata:
name: jobexample name: jobexample
labels: labels:
app: jobexample jobgroup: jobexample
item: $ITEM
spec: spec:
containers: containers:
- name: c - name: c
@ -75,12 +72,14 @@ In a real use case, the processing would be some substantial computation, such a
of a movie, or processing a range of rows in a database. The "$ITEM" parameter would specify for of a movie, or processing a range of rows in a database. The "$ITEM" parameter would specify for
example, the frame number or the row range. example, the frame number or the row range.
This Job has two labels. The first label, `app=jobexample`, distinguishes this group of jobs from This Job and its Pod template have a label: `jobgroup=jobexample`. There is nothing special
other groups of jobs (these are not shown, but there might be other ones). This label to the system about this label. This label
makes it convenient to operate on all the jobs in the group at once. The second label, with makes it convenient to operate on all the jobs in this group at once.
key `item`, distinguishes individual jobs in the group. Each Job object needs to have We also put the same label on the pod template so that we can check on all Pods of these Jobs
a unique label that no other job has. This is it. with a single command.
Neither of these label keys are special to kubernetes -- you can pick your own label scheme. After the job is created, the system will add more labels that distinguish one Job's pods
from another Job's pods.
Note that the label key `jobgroup` is not special to Kubernetes. you can pick your own label scheme.
Next, expand the template into multiple files, one for each item to be processed. Next, expand the template into multiple files, one for each item to be processed.
@ -113,27 +112,25 @@ job "process-item-cherry" created
Now, check on the jobs: Now, check on the jobs:
```console ```console
$ kubectl get jobs -l app=jobexample -L item $ kubectl get jobs -l app=jobexample
JOB CONTAINER(S) IMAGE(S) SELECTOR SUCCESSFUL ITEM JOB CONTAINER(S) IMAGE(S) SELECTOR SUCCESSFUL
process-item-apple c busybox app in (jobexample),item in (apple) 1 apple process-item-apple c busybox app in (jobexample),item in (apple) 1
process-item-banana c busybox app in (jobexample),item in (banana) 1 banana process-item-banana c busybox app in (jobexample),item in (banana) 1
process-item-cherry c busybox app in (jobexample),item in (cherry) 1 cherry process-item-cherry c busybox app in (jobexample),item in (cherry) 1
``` ```
Here we use the `-l` option to select all jobs that are part of this Here we use the `-l` option to select all jobs that are part of this
group of jobs. (There might be other unrelated jobs in the system that we group of jobs. (There might be other unrelated jobs in the system that we
do not care to see.) do not care to see.)
The `-L` option adds an extra column with just the `item` label value.
We can check on the pods as well using the same label selector: We can check on the pods as well using the same label selector:
```console ```console
$ kubectl get pods -l app=jobexample -L item $ kubectl get pods -l app=jobexample
NAME READY STATUS RESTARTS AGE ITEM NAME READY STATUS RESTARTS AGE
process-item-apple-kixwv 0/1 Completed 0 4m apple process-item-apple-kixwv 0/1 Completed 0 4m
process-item-banana-wrsf7 0/1 Completed 0 4m banana process-item-banana-wrsf7 0/1 Completed 0 4m
process-item-cherry-dnfu9 0/1 Completed 0 4m cherry process-item-cherry-dnfu9 0/1 Completed 0 4m
``` ```
There is not a single command to check on the output of all jobs at once, There is not a single command to check on the output of all jobs at once,
@ -170,21 +167,17 @@ First, download or paste the following template file to a file called `job.yaml.
{%- for p in params %} {%- for p in params %}
{%- set name = p["name"] %} {%- set name = p["name"] %}
{%- set url = p["url"] %} {%- set url = p["url"] %}
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: jobexample-{{ name }} name: jobexample-{{ name }}
labels:
jobgroup: jobexample
spec: spec:
selector:
matchLabels:
app: jobexample
item: {{ name }}
template: template:
metadata:
name: jobexample name: jobexample
labels: labels:
app: jobexample jobgroup: jobexample
item: {{ name }}
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -5,21 +5,17 @@
{%- for p in params %} {%- for p in params %}
{%- set name = p["name"] %} {%- set name = p["name"] %}
{%- set url = p["url"] %} {%- set url = p["url"] %}
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: jobexample-{{ name }} name: jobexample-{{ name }}
labels:
jobgroup: jobexample
spec: spec:
selector:
matchLabels:
app: jobexample
item: {{ name }}
template: template:
metadata:
name: jobexample name: jobexample
labels: labels:
app: jobexample jobgroup: jobexample
item: {{ name }}
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -1,18 +1,15 @@
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: process-item-$ITEM name: process-item-$ITEM
labels:
jobgroup: jobexample
spec: spec:
selector:
matchLabels:
app: jobexample
item: $ITEM
template: template:
metadata: metadata:
name: jobexample name: jobexample
labels: labels:
app: jobexample jobgroup: jobexample
item: $ITEM
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -262,21 +262,16 @@ image to match the name you used, and call it `./job.yaml`.
<!-- BEGIN MUNGE: EXAMPLE job.yaml --> <!-- BEGIN MUNGE: EXAMPLE job.yaml -->
```yaml ```yaml
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: job-wq-1 name: job-wq-1
spec: spec:
selector:
matchLabels:
app: job-wq-1
completions: 8 completions: 8
parallelism: 2 parallelism: 2
template: template:
metadata: metadata:
name: job-wq-1 name: job-wq-1
labels:
app: job-wq-1
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -1,18 +1,13 @@
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: job-wq-1 name: job-wq-1
spec: spec:
selector:
matchLabels:
app: job-wq-1
completions: 8 completions: 8
parallelism: 2 parallelism: 2
template: template:
metadata: metadata:
name: job-wq-1 name: job-wq-1
labels:
app: job-wq-1
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -217,20 +217,15 @@ Here is the job definition:
<!-- BEGIN MUNGE: EXAMPLE job.yaml --> <!-- BEGIN MUNGE: EXAMPLE job.yaml -->
```yaml ```yaml
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: job-wq-2 name: job-wq-2
spec: spec:
selector:
matchLabels:
app: job-wq-2
parallelism: 2 parallelism: 2
template: template:
metadata: metadata:
name: job-wq-2 name: job-wq-2
labels:
app: job-wq-2
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -1,17 +1,12 @@
apiVersion: extensions/v1beta1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
name: job-wq-2 name: job-wq-2
spec: spec:
selector:
matchLabels:
app: job-wq-2
parallelism: 2 parallelism: 2
template: template:
metadata: metadata:
name: job-wq-2 name: job-wq-2
labels:
app: job-wq-2
spec: spec:
containers: containers:
- name: c - name: c

View File

@ -162,6 +162,11 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source)
parallelism := int(c.Rand.Int31()) parallelism := int(c.Rand.Int31())
j.Completions = &completions j.Completions = &completions
j.Parallelism = &parallelism j.Parallelism = &parallelism
if c.Rand.Int31()%2 == 0 {
j.ManualSelector = newBool(true)
} else {
j.ManualSelector = nil
}
}, },
func(j *api.List, c fuzz.Continue) { func(j *api.List, c fuzz.Continue) {
c.FuzzNoCustom(j) // fuzz self without calling this function again c.FuzzNoCustom(j) // fuzz self without calling this function again
@ -411,3 +416,9 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source)
) )
return f return f
} }
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}

View File

@ -2681,6 +2681,12 @@ func autoConvert_v1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.J
} else { } else {
out.Selector = nil out.Selector = nil
} }
if in.ManualSelector != nil {
out.ManualSelector = new(bool)
*out.ManualSelector = *in.ManualSelector
} else {
out.ManualSelector = nil
}
if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
return err return err
} }
@ -2885,6 +2891,12 @@ func autoConvert_extensions_JobSpec_To_v1_JobSpec(in *extensions.JobSpec, out *J
} else { } else {
out.Selector = nil out.Selector = nil
} }
if in.ManualSelector != nil {
out.ManualSelector = new(bool)
*out.ManualSelector = *in.ManualSelector
} else {
out.ManualSelector = nil
}
if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
return err return err
} }

View File

@ -1076,6 +1076,12 @@ func deepCopy_v1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) error {
} else { } else {
out.Selector = nil out.Selector = nil
} }
if in.ManualSelector != nil {
out.ManualSelector = new(bool)
*out.ManualSelector = *in.ManualSelector
} else {
out.ManualSelector = nil
}
if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil {
return err return err
} }

View File

@ -23,18 +23,6 @@ import (
func addDefaultingFuncs(scheme *runtime.Scheme) { func addDefaultingFuncs(scheme *runtime.Scheme) {
scheme.AddDefaultingFuncs( scheme.AddDefaultingFuncs(
func(obj *Job) { func(obj *Job) {
labels := obj.Spec.Template.Labels
// TODO: support templates defined elsewhere when we support them in the API
if labels != nil {
if obj.Spec.Selector == nil {
obj.Spec.Selector = &LabelSelector{
MatchLabels: labels,
}
}
if len(obj.Labels) == 0 {
obj.Labels = labels
}
}
// For a non-parallel job, you can leave both `.spec.completions` and // For a non-parallel job, you can leave both `.spec.completions` and
// `.spec.parallelism` unset. When both are unset, both are defaulted to 1. // `.spec.parallelism` unset. When both are unset, both are defaulted to 1.
if obj.Spec.Completions == nil && obj.Spec.Parallelism == nil { if obj.Spec.Completions == nil && obj.Spec.Parallelism == nil {

View File

@ -778,16 +778,17 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [5]bool var yyq2 [6]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[0] = x.Parallelism != nil yyq2[0] = x.Parallelism != nil
yyq2[1] = x.Completions != nil yyq2[1] = x.Completions != nil
yyq2[2] = x.ActiveDeadlineSeconds != nil yyq2[2] = x.ActiveDeadlineSeconds != nil
yyq2[3] = x.Selector != nil yyq2[3] = x.Selector != nil
yyq2[4] = x.ManualSelector != nil
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(5) r.EncodeArrayStart(6)
} else { } else {
yynn2 = 1 yynn2 = 1
for _, b := range yyq2 { for _, b := range yyq2 {
@ -928,14 +929,49 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234) z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy22 := &x.Template if yyq2[4] {
yy22.CodecEncodeSelf(e) if x.ManualSelector == nil {
r.EncodeNil()
} else {
yy22 := *x.ManualSelector
yym23 := z.EncBinary()
_ = yym23
if false {
} else {
r.EncodeBool(bool(yy22))
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[4] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("manualSelector"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.ManualSelector == nil {
r.EncodeNil()
} else {
yy24 := *x.ManualSelector
yym25 := z.EncBinary()
_ = yym25
if false {
} else {
r.EncodeBool(bool(yy24))
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy27 := &x.Template
yy27.CodecEncodeSelf(e)
} else { } else {
z.EncSendContainerState(codecSelfer_containerMapKey1234) z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("template")) r.EncodeString(codecSelferC_UTF81234, string("template"))
z.EncSendContainerState(codecSelfer_containerMapValue1234) z.EncSendContainerState(codecSelfer_containerMapValue1234)
yy24 := &x.Template yy29 := &x.Template
yy24.CodecEncodeSelf(e) yy29.CodecEncodeSelf(e)
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
@ -1057,12 +1093,28 @@ func (x *JobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
} }
x.Selector.CodecDecodeSelf(d) x.Selector.CodecDecodeSelf(d)
} }
case "manualSelector":
if r.TryDecodeAsNil() {
if x.ManualSelector != nil {
x.ManualSelector = nil
}
} else {
if x.ManualSelector == nil {
x.ManualSelector = new(bool)
}
yym12 := z.DecBinary()
_ = yym12
if false {
} else {
*((*bool)(x.ManualSelector)) = r.DecodeBool()
}
}
case "template": case "template":
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Template = pkg2_v1.PodTemplateSpec{} x.Template = pkg2_v1.PodTemplateSpec{}
} else { } else {
yyv11 := &x.Template yyv13 := &x.Template
yyv11.CodecDecodeSelf(d) yyv13.CodecDecodeSelf(d)
} }
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
@ -1075,16 +1127,16 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj12 int var yyj14 int
var yyb12 bool var yyb14 bool
var yyhl12 bool = l >= 0 var yyhl14 bool = l >= 0
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -1097,20 +1149,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Parallelism == nil { if x.Parallelism == nil {
x.Parallelism = new(int32) x.Parallelism = new(int32)
} }
yym14 := z.DecBinary() yym16 := z.DecBinary()
_ = yym14 _ = yym16
if false { if false {
} else { } else {
*((*int32)(x.Parallelism)) = int32(r.DecodeInt(32)) *((*int32)(x.Parallelism)) = int32(r.DecodeInt(32))
} }
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -1123,20 +1175,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Completions == nil { if x.Completions == nil {
x.Completions = new(int32) x.Completions = new(int32)
} }
yym16 := z.DecBinary() yym18 := z.DecBinary()
_ = yym16 _ = yym18
if false { if false {
} else { } else {
*((*int32)(x.Completions)) = int32(r.DecodeInt(32)) *((*int32)(x.Completions)) = int32(r.DecodeInt(32))
} }
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -1149,20 +1201,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.ActiveDeadlineSeconds == nil { if x.ActiveDeadlineSeconds == nil {
x.ActiveDeadlineSeconds = new(int64) x.ActiveDeadlineSeconds = new(int64)
} }
yym18 := z.DecBinary() yym20 := z.DecBinary()
_ = yym18 _ = yym20
if false { if false {
} else { } else {
*((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64))
} }
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -1177,13 +1229,39 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} }
x.Selector.CodecDecodeSelf(d) x.Selector.CodecDecodeSelf(d)
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
if x.ManualSelector != nil {
x.ManualSelector = nil
}
} else {
if x.ManualSelector == nil {
x.ManualSelector = new(bool)
}
yym23 := z.DecBinary()
_ = yym23
if false {
} else {
*((*bool)(x.ManualSelector)) = r.DecodeBool()
}
}
yyj14++
if yyhl14 {
yyb14 = yyj14 > l
} else {
yyb14 = r.CheckBreak()
}
if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -1191,21 +1269,21 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Template = pkg2_v1.PodTemplateSpec{} x.Template = pkg2_v1.PodTemplateSpec{}
} else { } else {
yyv20 := &x.Template yyv24 := &x.Template
yyv20.CodecDecodeSelf(d) yyv24.CodecDecodeSelf(d)
} }
for { for {
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj12-1, "") z.DecStructFieldNotFound(yyj14-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
@ -2789,7 +2867,7 @@ func (x codecSelfer1234) decSliceJob(v *[]Job, d *codec1978.Decoder) {
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 632) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 640)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]

View File

@ -63,6 +63,7 @@ type JobSpec struct {
// pod signals the success of all pods, and allows parallelism to have any positive // pod signals the success of all pods, and allows parallelism to have any positive
// value. Setting to 1 means that parallelism is limited to 1 and the success of that // value. Setting to 1 means that parallelism is limited to 1 and the success of that
// pod signals the success of the job. // pod signals the success of the job.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md
Completions *int32 `json:"completions,omitempty"` Completions *int32 `json:"completions,omitempty"`
// Optional duration in seconds relative to the startTime that the job may be active // Optional duration in seconds relative to the startTime that the job may be active
@ -70,9 +71,22 @@ type JobSpec struct {
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
// Selector is a label query over pods that should match the pod count. // Selector is a label query over pods that should match the pod count.
// Normally, the system sets this field for you.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector *LabelSelector `json:"selector,omitempty"` Selector *LabelSelector `json:"selector,omitempty"`
// 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: http://releases.k8s.io/HEAD/docs/design/selector-generation.md
ManualSelector *bool `json:"manualSelector,omitempty"`
// Template is the object that describes the pod that will be created when // Template is the object that describes the pod that will be created when
// executing a job. // executing a job.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md

View File

@ -65,9 +65,10 @@ func (JobList) SwaggerDoc() map[string]string {
var map_JobSpec = map[string]string{ var map_JobSpec = map[string]string{
"": "JobSpec describes how the job execution will look like.", "": "JobSpec describes how the job execution will look like.",
"parallelism": "Parallelism specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", "parallelism": "Parallelism specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md",
"completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job.", "completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md",
"activeDeadlineSeconds": "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", "activeDeadlineSeconds": "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",
"selector": "Selector is a label query over pods that should match the pod count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", "selector": "Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors",
"manualSelector": "ManualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md",
"template": "Template is the object that describes the pod that will be created when executing a job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", "template": "Template is the object that describes the pod that will be created when executing a job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md",
} }

View File

@ -9925,16 +9925,17 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [5]bool var yyq2 [6]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[0] = x.Parallelism != nil yyq2[0] = x.Parallelism != nil
yyq2[1] = x.Completions != nil yyq2[1] = x.Completions != nil
yyq2[2] = x.ActiveDeadlineSeconds != nil yyq2[2] = x.ActiveDeadlineSeconds != nil
yyq2[3] = x.Selector != nil yyq2[3] = x.Selector != nil
yyq2[4] = x.ManualSelector != nil
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(5) r.EncodeArrayStart(6)
} else { } else {
yynn2 = 1 yynn2 = 1
for _, b := range yyq2 { for _, b := range yyq2 {
@ -10087,14 +10088,49 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234) z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy22 := &x.Template if yyq2[4] {
yy22.CodecEncodeSelf(e) if x.ManualSelector == nil {
r.EncodeNil()
} else {
yy22 := *x.ManualSelector
yym23 := z.EncBinary()
_ = yym23
if false {
} else {
r.EncodeBool(bool(yy22))
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[4] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("manualSelector"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.ManualSelector == nil {
r.EncodeNil()
} else {
yy24 := *x.ManualSelector
yym25 := z.EncBinary()
_ = yym25
if false {
} else {
r.EncodeBool(bool(yy24))
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy27 := &x.Template
yy27.CodecEncodeSelf(e)
} else { } else {
z.EncSendContainerState(codecSelfer_containerMapKey1234) z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("template")) r.EncodeString(codecSelferC_UTF81234, string("template"))
z.EncSendContainerState(codecSelfer_containerMapValue1234) z.EncSendContainerState(codecSelfer_containerMapValue1234)
yy24 := &x.Template yy29 := &x.Template
yy24.CodecEncodeSelf(e) yy29.CodecEncodeSelf(e)
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
@ -10222,12 +10258,28 @@ func (x *JobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
z.DecFallback(x.Selector, false) z.DecFallback(x.Selector, false)
} }
} }
case "manualSelector":
if r.TryDecodeAsNil() {
if x.ManualSelector != nil {
x.ManualSelector = nil
}
} else {
if x.ManualSelector == nil {
x.ManualSelector = new(bool)
}
yym13 := z.DecBinary()
_ = yym13
if false {
} else {
*((*bool)(x.ManualSelector)) = r.DecodeBool()
}
}
case "template": case "template":
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Template = pkg2_api.PodTemplateSpec{} x.Template = pkg2_api.PodTemplateSpec{}
} else { } else {
yyv12 := &x.Template yyv14 := &x.Template
yyv12.CodecDecodeSelf(d) yyv14.CodecDecodeSelf(d)
} }
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
@ -10240,16 +10292,16 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj13 int var yyj15 int
var yyb13 bool var yyb15 bool
var yyhl13 bool = l >= 0 var yyhl15 bool = l >= 0
yyj13++ yyj15++
if yyhl13 { if yyhl15 {
yyb13 = yyj13 > l yyb15 = yyj15 > l
} else { } else {
yyb13 = r.CheckBreak() yyb15 = r.CheckBreak()
} }
if yyb13 { if yyb15 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10262,20 +10314,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Parallelism == nil { if x.Parallelism == nil {
x.Parallelism = new(int) x.Parallelism = new(int)
} }
yym15 := z.DecBinary() yym17 := z.DecBinary()
_ = yym15 _ = yym17
if false { if false {
} else { } else {
*((*int)(x.Parallelism)) = int(r.DecodeInt(codecSelferBitsize1234)) *((*int)(x.Parallelism)) = int(r.DecodeInt(codecSelferBitsize1234))
} }
} }
yyj13++ yyj15++
if yyhl13 { if yyhl15 {
yyb13 = yyj13 > l yyb15 = yyj15 > l
} else { } else {
yyb13 = r.CheckBreak() yyb15 = r.CheckBreak()
} }
if yyb13 { if yyb15 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10288,20 +10340,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Completions == nil { if x.Completions == nil {
x.Completions = new(int) x.Completions = new(int)
} }
yym17 := z.DecBinary() yym19 := z.DecBinary()
_ = yym17 _ = yym19
if false { if false {
} else { } else {
*((*int)(x.Completions)) = int(r.DecodeInt(codecSelferBitsize1234)) *((*int)(x.Completions)) = int(r.DecodeInt(codecSelferBitsize1234))
} }
} }
yyj13++ yyj15++
if yyhl13 { if yyhl15 {
yyb13 = yyj13 > l yyb15 = yyj15 > l
} else { } else {
yyb13 = r.CheckBreak() yyb15 = r.CheckBreak()
} }
if yyb13 { if yyb15 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10314,20 +10366,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.ActiveDeadlineSeconds == nil { if x.ActiveDeadlineSeconds == nil {
x.ActiveDeadlineSeconds = new(int64) x.ActiveDeadlineSeconds = new(int64)
} }
yym19 := z.DecBinary() yym21 := z.DecBinary()
_ = yym19 _ = yym21
if false { if false {
} else { } else {
*((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64))
} }
} }
yyj13++ yyj15++
if yyhl13 { if yyhl15 {
yyb13 = yyj13 > l yyb15 = yyj15 > l
} else { } else {
yyb13 = r.CheckBreak() yyb15 = r.CheckBreak()
} }
if yyb13 { if yyb15 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10340,21 +10392,47 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Selector == nil { if x.Selector == nil {
x.Selector = new(pkg1_unversioned.LabelSelector) x.Selector = new(pkg1_unversioned.LabelSelector)
} }
yym21 := z.DecBinary() yym23 := z.DecBinary()
_ = yym21 _ = yym23
if false { if false {
} else if z.HasExtensions() && z.DecExt(x.Selector) { } else if z.HasExtensions() && z.DecExt(x.Selector) {
} else { } else {
z.DecFallback(x.Selector, false) z.DecFallback(x.Selector, false)
} }
} }
yyj13++ yyj15++
if yyhl13 { if yyhl15 {
yyb13 = yyj13 > l yyb15 = yyj15 > l
} else { } else {
yyb13 = r.CheckBreak() yyb15 = r.CheckBreak()
} }
if yyb13 { if yyb15 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
if x.ManualSelector != nil {
x.ManualSelector = nil
}
} else {
if x.ManualSelector == nil {
x.ManualSelector = new(bool)
}
yym25 := z.DecBinary()
_ = yym25
if false {
} else {
*((*bool)(x.ManualSelector)) = r.DecodeBool()
}
}
yyj15++
if yyhl15 {
yyb15 = yyj15 > l
} else {
yyb15 = r.CheckBreak()
}
if yyb15 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10362,21 +10440,21 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Template = pkg2_api.PodTemplateSpec{} x.Template = pkg2_api.PodTemplateSpec{}
} else { } else {
yyv22 := &x.Template yyv26 := &x.Template
yyv22.CodecDecodeSelf(d) yyv26.CodecDecodeSelf(d)
} }
for { for {
yyj13++ yyj15++
if yyhl13 { if yyhl15 {
yyb13 = yyj13 > l yyb15 = yyj15 > l
} else { } else {
yyb13 = r.CheckBreak() yyb15 = r.CheckBreak()
} }
if yyb13 { if yyb15 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj13-1, "") z.DecStructFieldNotFound(yyj15-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
@ -19286,7 +19364,7 @@ func (x codecSelfer1234) decSliceJob(v *[]Job, d *codec1978.Decoder) {
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 616) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 624)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]

View File

@ -535,7 +535,10 @@ type JobSpec struct {
Parallelism *int `json:"parallelism,omitempty"` Parallelism *int `json:"parallelism,omitempty"`
// Completions specifies the desired number of successfully finished pods the // Completions specifies the desired number of successfully finished pods the
// job should be run with. When unset, any pod exiting signals the job to complete. // job should be run with. Setting to nil means that the success of any
// pod signals the success of all pods, and allows parallelism to have any positive
// value. Setting to 1 means that parallelism is limited to 1 and the success of that
// pod signals the success of the job.
Completions *int `json:"completions,omitempty"` Completions *int `json:"completions,omitempty"`
// Optional duration in seconds relative to the startTime that the job may be active // Optional duration in seconds relative to the startTime that the job may be active
@ -543,8 +546,20 @@ type JobSpec struct {
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
// Selector is a label query over pods that should match the pod count. // Selector is a label query over pods that should match the pod count.
// Normally, the system sets this field for you.
Selector *unversioned.LabelSelector `json:"selector,omitempty"` Selector *unversioned.LabelSelector `json:"selector,omitempty"`
// 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.
ManualSelector *bool `json:"manualSelector,omitempty"`
// Template is the object that describes the pod that will be created when // Template is the object that describes the pod that will be created when
// executing a job. // executing a job.
Template api.PodTemplateSpec `json:"template"` Template api.PodTemplateSpec `json:"template"`

View File

@ -42,6 +42,8 @@ func addConversionFuncs(scheme *runtime.Scheme) {
Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment,
Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec, Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec,
Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec, Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec,
Convert_extensions_JobSpec_To_v1beta1_JobSpec,
Convert_v1beta1_JobSpec_To_extensions_JobSpec,
) )
if err != nil { if err != nil {
// If one of the conversion functions is malformed, detect it immediately. // If one of the conversion functions is malformed, detect it immediately.
@ -277,3 +279,107 @@ func Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *ReplicaSetS
} }
return nil return nil
} }
func Convert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.JobSpec))(in)
}
if in.Parallelism != nil {
out.Parallelism = new(int32)
*out.Parallelism = int32(*in.Parallelism)
} else {
out.Parallelism = nil
}
if in.Completions != nil {
out.Completions = new(int32)
*out.Completions = int32(*in.Completions)
} else {
out.Completions = nil
}
if in.ActiveDeadlineSeconds != nil {
out.ActiveDeadlineSeconds = new(int64)
*out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds
} else {
out.ActiveDeadlineSeconds = nil
}
// unable to generate simple pointer conversion for unversioned.LabelSelector -> v1beta1.LabelSelector
if in.Selector != nil {
out.Selector = new(LabelSelector)
if err := Convert_unversioned_LabelSelector_To_v1beta1_LabelSelector(in.Selector, out.Selector, s); err != nil {
return err
}
} else {
out.Selector = nil
}
// BEGIN non-standard conversion
// autoSelector has opposite meaning as manualSelector.
// in both cases, unset means false, and unset is always preferred to false.
// unset vs set-false distinction is not preserved.
manualSelector := in.ManualSelector != nil && *in.ManualSelector
autoSelector := !manualSelector
if autoSelector {
out.AutoSelector = new(bool)
*out.AutoSelector = true
} else {
out.AutoSelector = nil
}
// END non-standard conversion
if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
return err
}
return nil
}
func Convert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*JobSpec))(in)
}
if in.Parallelism != nil {
out.Parallelism = new(int)
*out.Parallelism = int(*in.Parallelism)
} else {
out.Parallelism = nil
}
if in.Completions != nil {
out.Completions = new(int)
*out.Completions = int(*in.Completions)
} else {
out.Completions = nil
}
if in.ActiveDeadlineSeconds != nil {
out.ActiveDeadlineSeconds = new(int64)
*out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds
} else {
out.ActiveDeadlineSeconds = nil
}
// unable to generate simple pointer conversion for v1beta1.LabelSelector -> unversioned.LabelSelector
if in.Selector != nil {
out.Selector = new(unversioned.LabelSelector)
if err := Convert_v1beta1_LabelSelector_To_unversioned_LabelSelector(in.Selector, out.Selector, s); err != nil {
return err
}
} else {
out.Selector = nil
}
// BEGIN non-standard conversion
// autoSelector has opposite meaning as manualSelector.
// in both cases, unset means false, and unset is always preferred to false.
// unset vs set-false distinction is not preserved.
autoSelector := bool(in.AutoSelector != nil && *in.AutoSelector)
manualSelector := !autoSelector
if manualSelector {
out.ManualSelector = new(bool)
*out.ManualSelector = true
} else {
out.ManualSelector = nil
}
// END non-standard conversion
if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
return err
}
return nil
}

View File

@ -3460,16 +3460,13 @@ func autoConvert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, o
} else { } else {
out.Selector = nil out.Selector = nil
} }
// in.ManualSelector has no peer in out
if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
return err return err
} }
return nil return nil
} }
func Convert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error {
return autoConvert_extensions_JobSpec_To_v1beta1_JobSpec(in, out, s)
}
func autoConvert_extensions_JobStatus_To_v1beta1_JobStatus(in *extensions.JobStatus, out *JobStatus, s conversion.Scope) error { func autoConvert_extensions_JobStatus_To_v1beta1_JobStatus(in *extensions.JobStatus, out *JobStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.JobStatus))(in) defaulting.(func(*extensions.JobStatus))(in)
@ -4730,16 +4727,13 @@ func autoConvert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensi
} else { } else {
out.Selector = nil out.Selector = nil
} }
// in.AutoSelector has no peer in out
if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil {
return err return err
} }
return nil return nil
} }
func Convert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error {
return autoConvert_v1beta1_JobSpec_To_extensions_JobSpec(in, out, s)
}
func autoConvert_v1beta1_JobStatus_To_extensions_JobStatus(in *JobStatus, out *extensions.JobStatus, s conversion.Scope) error { func autoConvert_v1beta1_JobStatus_To_extensions_JobStatus(in *JobStatus, out *extensions.JobStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*JobStatus))(in) defaulting.(func(*JobStatus))(in)

View File

@ -0,0 +1,83 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1_test
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
versioned "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
)
// TestJobSpecConversion tests that ManualSelector and AutoSelector
// are handled correctly.
func TestJobSpecConversion(t *testing.T) {
pTrue := new(bool)
*pTrue = true
pFalse := new(bool)
*pFalse = false
// False or nil convert to true.
// True converts to nil.
tests := []struct {
in *bool
expectOut *bool
}{
{
in: nil,
expectOut: pTrue,
},
{
in: pFalse,
expectOut: pTrue,
},
{
in: pTrue,
expectOut: nil,
},
}
// Test internal -> v1beta1.
for _, test := range tests {
i := &extensions.JobSpec{
ManualSelector: test.in,
}
v := versioned.JobSpec{}
if err := api.Scheme.Convert(i, &v); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expectOut, v.AutoSelector) {
t.Fatalf("want v1beta1.AutoSelector %v, got %v", test.expectOut, v.AutoSelector)
}
}
// Test v1beta1 -> internal.
for _, test := range tests {
i := &versioned.JobSpec{
AutoSelector: test.in,
}
e := extensions.JobSpec{}
if err := api.Scheme.Convert(i, &e); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expectOut, e.ManualSelector) {
t.Fatalf("want extensions.ManualSelector %v, got %v", test.expectOut, e.ManualSelector)
}
}
}

View File

@ -1565,6 +1565,12 @@ func deepCopy_v1beta1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) er
} else { } else {
out.Selector = nil out.Selector = nil
} }
if in.AutoSelector != nil {
out.AutoSelector = new(bool)
*out.AutoSelector = *in.AutoSelector
} else {
out.AutoSelector = nil
}
if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil {
return err return err
} }

View File

@ -9935,16 +9935,17 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [5]bool var yyq2 [6]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[0] = x.Parallelism != nil yyq2[0] = x.Parallelism != nil
yyq2[1] = x.Completions != nil yyq2[1] = x.Completions != nil
yyq2[2] = x.ActiveDeadlineSeconds != nil yyq2[2] = x.ActiveDeadlineSeconds != nil
yyq2[3] = x.Selector != nil yyq2[3] = x.Selector != nil
yyq2[4] = x.AutoSelector != nil
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(5) r.EncodeArrayStart(6)
} else { } else {
yynn2 = 1 yynn2 = 1
for _, b := range yyq2 { for _, b := range yyq2 {
@ -10085,14 +10086,49 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234) z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy22 := &x.Template if yyq2[4] {
yy22.CodecEncodeSelf(e) if x.AutoSelector == nil {
r.EncodeNil()
} else {
yy22 := *x.AutoSelector
yym23 := z.EncBinary()
_ = yym23
if false {
} else {
r.EncodeBool(bool(yy22))
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[4] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("autoSelector"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.AutoSelector == nil {
r.EncodeNil()
} else {
yy24 := *x.AutoSelector
yym25 := z.EncBinary()
_ = yym25
if false {
} else {
r.EncodeBool(bool(yy24))
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy27 := &x.Template
yy27.CodecEncodeSelf(e)
} else { } else {
z.EncSendContainerState(codecSelfer_containerMapKey1234) z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("template")) r.EncodeString(codecSelferC_UTF81234, string("template"))
z.EncSendContainerState(codecSelfer_containerMapValue1234) z.EncSendContainerState(codecSelfer_containerMapValue1234)
yy24 := &x.Template yy29 := &x.Template
yy24.CodecEncodeSelf(e) yy29.CodecEncodeSelf(e)
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
@ -10214,12 +10250,28 @@ func (x *JobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
} }
x.Selector.CodecDecodeSelf(d) x.Selector.CodecDecodeSelf(d)
} }
case "autoSelector":
if r.TryDecodeAsNil() {
if x.AutoSelector != nil {
x.AutoSelector = nil
}
} else {
if x.AutoSelector == nil {
x.AutoSelector = new(bool)
}
yym12 := z.DecBinary()
_ = yym12
if false {
} else {
*((*bool)(x.AutoSelector)) = r.DecodeBool()
}
}
case "template": case "template":
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Template = pkg2_v1.PodTemplateSpec{} x.Template = pkg2_v1.PodTemplateSpec{}
} else { } else {
yyv11 := &x.Template yyv13 := &x.Template
yyv11.CodecDecodeSelf(d) yyv13.CodecDecodeSelf(d)
} }
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
@ -10232,16 +10284,16 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj12 int var yyj14 int
var yyb12 bool var yyb14 bool
var yyhl12 bool = l >= 0 var yyhl14 bool = l >= 0
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10254,20 +10306,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Parallelism == nil { if x.Parallelism == nil {
x.Parallelism = new(int32) x.Parallelism = new(int32)
} }
yym14 := z.DecBinary() yym16 := z.DecBinary()
_ = yym14 _ = yym16
if false { if false {
} else { } else {
*((*int32)(x.Parallelism)) = int32(r.DecodeInt(32)) *((*int32)(x.Parallelism)) = int32(r.DecodeInt(32))
} }
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10280,20 +10332,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.Completions == nil { if x.Completions == nil {
x.Completions = new(int32) x.Completions = new(int32)
} }
yym16 := z.DecBinary() yym18 := z.DecBinary()
_ = yym16 _ = yym18
if false { if false {
} else { } else {
*((*int32)(x.Completions)) = int32(r.DecodeInt(32)) *((*int32)(x.Completions)) = int32(r.DecodeInt(32))
} }
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10306,20 +10358,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if x.ActiveDeadlineSeconds == nil { if x.ActiveDeadlineSeconds == nil {
x.ActiveDeadlineSeconds = new(int64) x.ActiveDeadlineSeconds = new(int64)
} }
yym18 := z.DecBinary() yym20 := z.DecBinary()
_ = yym18 _ = yym20
if false { if false {
} else { } else {
*((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64))
} }
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10334,13 +10386,39 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} }
x.Selector.CodecDecodeSelf(d) x.Selector.CodecDecodeSelf(d)
} }
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
if x.AutoSelector != nil {
x.AutoSelector = nil
}
} else {
if x.AutoSelector == nil {
x.AutoSelector = new(bool)
}
yym23 := z.DecBinary()
_ = yym23
if false {
} else {
*((*bool)(x.AutoSelector)) = r.DecodeBool()
}
}
yyj14++
if yyhl14 {
yyb14 = yyj14 > l
} else {
yyb14 = r.CheckBreak()
}
if yyb14 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -10348,21 +10426,21 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Template = pkg2_v1.PodTemplateSpec{} x.Template = pkg2_v1.PodTemplateSpec{}
} else { } else {
yyv20 := &x.Template yyv24 := &x.Template
yyv20.CodecDecodeSelf(d) yyv24.CodecDecodeSelf(d)
} }
for { for {
yyj12++ yyj14++
if yyhl12 { if yyhl14 {
yyb12 = yyj12 > l yyb14 = yyj14 > l
} else { } else {
yyb12 = r.CheckBreak() yyb14 = r.CheckBreak()
} }
if yyb12 { if yyb14 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj12-1, "") z.DecStructFieldNotFound(yyj14-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
@ -20615,7 +20693,7 @@ func (x codecSelfer1234) decSliceJob(v *[]Job, d *codec1978.Decoder) {
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 632) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 640)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]

View File

@ -543,6 +543,7 @@ type JobSpec struct {
// pod signals the success of all pods, and allows parallelism to have any positive // pod signals the success of all pods, and allows parallelism to have any positive
// value. Setting to 1 means that parallelism is limited to 1 and the success of that // value. Setting to 1 means that parallelism is limited to 1 and the success of that
// pod signals the success of the job. // pod signals the success of the job.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md
Completions *int32 `json:"completions,omitempty"` Completions *int32 `json:"completions,omitempty"`
// Optional duration in seconds relative to the startTime that the job may be active // Optional duration in seconds relative to the startTime that the job may be active
@ -550,9 +551,17 @@ type JobSpec struct {
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
// Selector is a label query over pods that should match the pod count. // Selector is a label query over pods that should match the pod count.
// Normally, the system sets this field for you.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector *LabelSelector `json:"selector,omitempty"` Selector *LabelSelector `json:"selector,omitempty"`
// AutoSelector controls generation of pod labels and pod selectors.
// It was not present in the original extensions/v1beta1 Job definition, but exists
// to allow conversion from batch/v1 Jobs, where it corresponds to, but has the opposite
// meaning as, ManualSelector.
// More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md
AutoSelector *bool `json:"autoSelector,omitempty"`
// Template is the object that describes the pod that will be created when // Template is the object that describes the pod that will be created when
// executing a job. // executing a job.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md

View File

@ -417,9 +417,10 @@ func (JobList) SwaggerDoc() map[string]string {
var map_JobSpec = map[string]string{ var map_JobSpec = map[string]string{
"": "JobSpec describes how the job execution will look like.", "": "JobSpec describes how the job execution will look like.",
"parallelism": "Parallelism specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", "parallelism": "Parallelism specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md",
"completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job.", "completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md",
"activeDeadlineSeconds": "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", "activeDeadlineSeconds": "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",
"selector": "Selector is a label query over pods that should match the pod count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", "selector": "Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors",
"autoSelector": "AutoSelector controls generation of pod labels and pod selectors. It was not present in the original extensions/v1beta1 Job definition, but exists to allow conversion from batch/v1 Jobs, where it corresponds to, but has the opposite meaning as, ManualSelector. More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md",
"template": "Template is the object that describes the pod that will be created when executing a job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", "template": "Template is the object that describes the pod that will be created when executing a job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md",
} }

View File

@ -379,9 +379,62 @@ func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) fiel
return allErrs return allErrs
} }
// TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and
// move to new location. Replace extensions.Job with an interface.
//
// ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object
// metadata, and the labels on the pod template are as generated.
func ValidateGeneratedSelector(obj *extensions.Job) field.ErrorList {
allErrs := field.ErrorList{}
if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector {
return allErrs
}
if obj.Spec.Selector == nil {
return allErrs // This case should already have been checked in caller. No need for more errors.
}
// If somehow uid was unset then we would get "controller-uid=" as the selector
// which is bad.
if obj.ObjectMeta.UID == "" {
allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
}
// If somehow uid was unset then we would get "controller-uid=" as the selector
// which is bad.
if obj.ObjectMeta.UID == "" {
allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), ""))
}
// If selector generation was requested, then expected labels must be
// present on pod template, and much match job's uid and name. The
// generated (not-manual) selectors/labels ensure no overlap with other
// controllers. The manual mode allows orphaning, adoption,
// backward-compatibility, and experimentation with new
// labeling/selection schemes. Automatic selector generation should
// have placed certain labels on the pod, but this could have failed if
// the user added coflicting labels. Validate that the expected
// generated ones are there.
allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...)
allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...)
expectedLabels := make(map[string]string)
expectedLabels["controller-uid"] = string(obj.UID)
expectedLabels["job-name"] = string(obj.Name)
// Whether manually or automatically generated, the selector of the job must match the pods it will produce.
if selector, err := unversioned.LabelSelectorAsSelector(obj.Spec.Selector); err == nil {
if !selector.Matches(labels.Set(expectedLabels)) {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated"))
}
}
return allErrs
}
func ValidateJob(job *extensions.Job) field.ErrorList { func ValidateJob(job *extensions.Job) field.ErrorList {
// Jobs and rcs have the same name validation // Jobs and rcs have the same name validation
allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata")) allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidateGeneratedSelector(job)...)
allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...) allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...)
return allErrs return allErrs
} }
@ -404,6 +457,7 @@ func ValidateJobSpec(spec *extensions.JobSpec, fldPath *field.Path) field.ErrorL
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
} }
// Whether manually or automatically generated, the selector of the job must match the pods it will produce.
if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil { if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil {
labels := labels.Set(spec.Template.Labels) labels := labels.Set(spec.Template.Labels)
if !selector.Matches(labels) { if !selector.Matches(labels) {

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/controller/podautoscaler" "k8s.io/kubernetes/pkg/controller/podautoscaler"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/intstr"
) )
@ -975,12 +976,15 @@ func TestValidateDeploymentRollback(t *testing.T) {
} }
func TestValidateJob(t *testing.T) { func TestValidateJob(t *testing.T) {
validSelector := &unversioned.LabelSelector{ validManualSelector := &unversioned.LabelSelector{
MatchLabels: map[string]string{"a": "b"}, MatchLabels: map[string]string{"a": "b"},
} }
validPodTemplateSpec := api.PodTemplateSpec{ validGeneratedSelector := &unversioned.LabelSelector{
MatchLabels: map[string]string{"collection-uid": "1a2b3c"},
}
validPodTemplateSpecForManual := api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector.MatchLabels, Labels: validManualSelector.MatchLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure, RestartPolicy: api.RestartPolicyOnFailure,
@ -988,21 +992,45 @@ func TestValidateJob(t *testing.T) {
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
}, },
} }
successCases := []extensions.Job{ validPodTemplateSpecForGenerated := api.PodTemplateSpec{
{ ObjectMeta: api.ObjectMeta{
Labels: validGeneratedSelector.MatchLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
}
successCases := map[string]extensions.Job{
"manual selector": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: validSelector, Selector: validManualSelector,
Template: validPodTemplateSpec, ManualSelector: newBool(true),
Template: validPodTemplateSpecForManual,
},
},
"generated selector": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Selector: validGeneratedSelector,
ManualSelector: newBool(true),
Template: validPodTemplateSpecForGenerated,
}, },
}, },
} }
for _, successCase := range successCases { for k, v := range successCases {
if errs := ValidateJob(&successCase); len(errs) != 0 { if errs := ValidateJob(&v); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success for %s: %v", k, errs)
} }
} }
negative := -1 negative := -1
@ -1012,51 +1040,59 @@ func TestValidateJob(t *testing.T) {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Parallelism: &negative, Parallelism: &negative,
Selector: validSelector, ManualSelector: newBool(true),
Template: validPodTemplateSpec, Template: validPodTemplateSpecForGenerated,
}, },
}, },
"spec.completions:must be greater than or equal to 0": { "spec.completions:must be greater than or equal to 0": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Completions: &negative, Completions: &negative,
Selector: validSelector, Selector: validManualSelector,
Template: validPodTemplateSpec, ManualSelector: newBool(true),
Template: validPodTemplateSpecForGenerated,
}, },
}, },
"spec.activeDeadlineSeconds:must be greater than or equal to 0": { "spec.activeDeadlineSeconds:must be greater than or equal to 0": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
ActiveDeadlineSeconds: &negative64, ActiveDeadlineSeconds: &negative64,
Selector: validSelector, Selector: validManualSelector,
Template: validPodTemplateSpec, ManualSelector: newBool(true),
Template: validPodTemplateSpecForGenerated,
}, },
}, },
"spec.selector:Required value": { "spec.selector:Required value": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Template: validPodTemplateSpec, Template: validPodTemplateSpecForGenerated,
}, },
}, },
"spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": { "spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: validSelector, Selector: validManualSelector,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"y": "z"}, Labels: map[string]string{"y": "z"},
@ -1069,16 +1105,39 @@ func TestValidateJob(t *testing.T) {
}, },
}, },
}, },
"spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Selector: validManualSelector,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"controller-uid": "4d5e6f"},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
},
},
"spec.template.spec.restartPolicy: Unsupported value": { "spec.template.spec.restartPolicy: Unsupported value": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: validSelector, Selector: validManualSelector,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector.MatchLabels, Labels: validManualSelector.MatchLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways, RestartPolicy: api.RestartPolicyAlways,
@ -2070,3 +2129,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
} }
} }
} }
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}

View File

@ -269,6 +269,7 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object
Selector: &unversioned.LabelSelector{ Selector: &unversioned.LabelSelector{
MatchLabels: labels, MatchLabels: labels,
}, },
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: labels, Labels: labels,
@ -607,3 +608,9 @@ func parseEnvs(envArray []string) ([]api.EnvVar, error) {
} }
return envs, nil return envs, nil
} }
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}

View File

@ -749,6 +749,7 @@ func TestGenerateJob(t *testing.T) {
Selector: &unversioned.LabelSelector{ Selector: &unversioned.LabelSelector{
MatchLabels: map[string]string{"foo": "bar", "baz": "blah"}, MatchLabels: map[string]string{"foo": "bar", "baz": "blah"},
}, },
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"foo": "bar", "baz": "blah"}, Labels: map[string]string{"foo": "bar", "baz": "blah"},

View File

@ -53,6 +53,7 @@ func validNewJob() *extensions.Job {
Selector: &unversioned.LabelSelector{ Selector: &unversioned.LabelSelector{
MatchLabels: map[string]string{"a": "b"}, MatchLabels: map[string]string{"a": "b"},
}, },
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"}, Labels: map[string]string{"a": "b"},
@ -165,3 +166,9 @@ func TestWatch(t *testing.T) {
} }
// TODO: test update /status // TODO: test update /status
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}

View File

@ -21,6 +21,7 @@ import (
"strconv" "strconv"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/validation" "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
@ -60,9 +61,63 @@ func (jobStrategy) PrepareForUpdate(obj, old runtime.Object) {
// Validate validates a new job. // Validate validates a new job.
func (jobStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { func (jobStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
job := obj.(*extensions.Job) job := obj.(*extensions.Job)
// TODO: move UID generation earlier and do this in defaulting logic?
if job.Spec.ManualSelector == nil || *job.Spec.ManualSelector == false {
generateSelector(job)
}
return validation.ValidateJob(job) return validation.ValidateJob(job)
} }
// generateSelector adds a selector to a job and labels to its template
// which can be used to uniquely identify the pods created by that job,
// if the user has requested this behavior.
func generateSelector(obj *extensions.Job) {
if obj.Spec.Template.Labels == nil {
obj.Spec.Template.Labels = make(map[string]string)
}
// The job-name label is unique except in cases that are expected to be
// quite uncommon, and is more user friendly than uid. So, we add it as
// a label.
_, found := obj.Spec.Template.Labels["job-name"]
if found {
// User asked us to not automatically generate a selector and labels,
// but set a possibly conflicting value. If there is a conflict,
// we will reject in validation.
} else {
obj.Spec.Template.Labels["job-name"] = string(obj.ObjectMeta.Name)
}
// The controller-uid label makes the pods that belong to this job
// only match this job.
_, found = obj.Spec.Template.Labels["controller-uid"]
if found {
// User asked us to automatically generate a selector and labels,
// but set a possibly conflicting value. If there is a conflict,
// we will reject in validation.
} else {
obj.Spec.Template.Labels["controller-uid"] = string(obj.ObjectMeta.UID)
}
// Select the controller-uid label. This is sufficient for uniqueness.
if obj.Spec.Selector == nil {
obj.Spec.Selector = &unversioned.LabelSelector{}
}
if obj.Spec.Selector.MatchLabels == nil {
obj.Spec.Selector.MatchLabels = make(map[string]string)
}
if _, found := obj.Spec.Selector.MatchLabels["controller-uid"]; !found {
obj.Spec.Selector.MatchLabels["controller-uid"] = string(obj.ObjectMeta.UID)
}
// If the user specified matchLabel controller-uid=$WRONGUID, then it should fail
// in validation, either because the selector does not match the pod template
// (controller-uid=$WRONGUID does not match controller-uid=$UID, which we applied
// above, or we will reject in validation because the template has the wrong
// labels.
}
// TODO: generalize generateSelector so it can work for other controller
// objects such as ReplicaSet. Can use pkg/api/meta to generically get the
// UID, but need some way to generically access the selector and pod labels
// fields.
// Canonicalize normalizes the object after validation. // Canonicalize normalizes the object after validation.
func (jobStrategy) Canonicalize(obj runtime.Object) { func (jobStrategy) Canonicalize(obj runtime.Object) {
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package job package job
import ( import (
"reflect"
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
@ -25,8 +26,15 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/types"
) )
func newBool(a bool) *bool {
r := new(bool)
*r = a
return r
}
func TestJobStrategy(t *testing.T) { func TestJobStrategy(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
if !Strategy.NamespaceScoped() { if !Strategy.NamespaceScoped() {
@ -57,6 +65,7 @@ func TestJobStrategy(t *testing.T) {
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: validSelector, Selector: validSelector,
Template: validPodTemplateSpec, Template: validPodTemplateSpec,
ManualSelector: newBool(true),
}, },
Status: extensions.JobStatus{ Status: extensions.JobStatus{
Active: 11, Active: 11,
@ -93,6 +102,56 @@ func TestJobStrategy(t *testing.T) {
} }
} }
func TestJobStrategyWithGeneration(t *testing.T) {
ctx := api.NewDefaultContext()
theUID := types.UID("1a2b3c4d5e6f7g8h9i0k")
validPodTemplateSpec := api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
}
job := &extensions.Job{
ObjectMeta: api.ObjectMeta{
Name: "myjob2",
Namespace: api.NamespaceDefault,
UID: theUID,
},
Spec: extensions.JobSpec{
Selector: nil,
Template: validPodTemplateSpec,
},
}
Strategy.PrepareForCreate(job)
errs := Strategy.Validate(ctx, job)
if len(errs) != 0 {
t.Errorf("Unexpected error validating %v", errs)
}
// Validate the stuff that validation should have validated.
if job.Spec.Selector == nil {
t.Errorf("Selector not generated")
}
expectedLabels := make(map[string]string)
expectedLabels["controller-uid"] = string(theUID)
if !reflect.DeepEqual(job.Spec.Selector.MatchLabels, expectedLabels) {
t.Errorf("Expected label selector not generated")
}
if job.Spec.Template.ObjectMeta.Labels == nil {
t.Errorf("Expected template labels not generated")
}
if v, ok := job.Spec.Template.ObjectMeta.Labels["job-name"]; !ok || v != "myjob2" {
t.Errorf("Expected template labels not present")
}
if v, ok := job.Spec.Template.ObjectMeta.Labels["controller-uid"]; !ok || v != string(theUID) {
t.Errorf("Expected template labels not present: ok: %v, v: %v", ok, v)
}
}
func TestJobStatusStrategy(t *testing.T) { func TestJobStatusStrategy(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
if !StatusStrategy.NamespaceScoped() { if !StatusStrategy.NamespaceScoped() {

View File

@ -206,6 +206,7 @@ func newTestJob(behavior, name string, rPol api.RestartPolicy, parallelism, comp
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Parallelism: &parallelism, Parallelism: &parallelism,
Completions: &completions, Completions: &completions,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{jobSelectorKey: name}, Labels: map[string]string{jobSelectorKey: name},
@ -312,3 +313,9 @@ func waitForJobFail(c *client.Client, ns, jobName string) error {
return false, nil return false, nil
}) })
} }
func newBool(val bool) *bool {
p := new(bool)
*p = val
return p
}

View File

@ -204,26 +204,15 @@ var jobV1 string = `
"kind": "Job", "kind": "Job",
"apiVersion": "batch/v1", "apiVersion": "batch/v1",
"metadata": { "metadata": {
"name": "pi", "name": "pi"
"labels": {
"app": "pi"
}
}, },
"spec": { "spec": {
"parallelism": 1, "parallelism": 1,
"completions": 1, "completions": 1,
"selector": {
"matchLabels": {
"app": "pi"
}
},
"template": { "template": {
"metadata": { "metadata": {
"name": "pi", "name": "pi",
"creationTimestamp": null, "creationTimestamp": null
"labels": {
"app": "pi"
}
}, },
"spec": { "spec": {
"containers": [ "containers": [