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": {
"type": "integer",
"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": {
"type": "integer",
@ -1000,7 +1000,11 @@
},
"selector": {
"$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": {
"$ref": "v1.PodTemplateSpec",

View File

@ -7326,7 +7326,7 @@
"completions": {
"type": "integer",
"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": {
"type": "integer",
@ -7335,7 +7335,11 @@
},
"selector": {
"$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": {
"$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>
<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">integer (int32)</p></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>
<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"><a href="#_v1beta1_labelselector">v1beta1.LabelSelector</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</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 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>

View File

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

View File

@ -47,6 +47,8 @@ Documentation for other releases can be found at
- [Controlling Parallelism](#controlling-parallelism)
- [Handling Pod and Container Failures](#handling-pod-and-container-failures)
- [Job Patterns](#job-patterns)
- [Advanced Usage](#advanced-usage)
- [Specifying your own pod selector](#specifying-your-own-pod-selector)
- [Alternatives](#alternatives)
- [Bare Pods](#bare-pods)
- [Replication Controller](#replication-controller)
@ -76,19 +78,14 @@ It takes around 10s to complete.
<!-- BEGIN MUNGE: EXAMPLE job.yaml -->
```yaml
apiVersion: extensions/v1beta1
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
selector:
matchLabels:
app: pi
template:
metadata:
name: pi
labels:
app: pi
spec:
containers:
- name: pi
@ -170,21 +167,9 @@ Only a [`RestartPolicy`](pod-states.md) equal to `Never` or `OnFailure` are allo
### 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
@ -323,6 +308,70 @@ Here, `W` is the number of work items.
| 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

View File

@ -32,7 +32,9 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
expvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/registry/job"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/util/yaml"
schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api"
@ -111,7 +113,10 @@ func validateObject(obj runtime.Object) (errors field.ErrorList) {
if t.Namespace == "" {
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:
if t.Namespace == "" {
t.Namespace = api.NamespaceDefault

View File

@ -40,21 +40,18 @@ First, create a template of a Job object:
<!-- BEGIN MUNGE: EXAMPLE job.yaml.txt -->
```
apiVersion: extensions/v1beta1
apiVersion: batch/v1
kind: Job
metadata:
name: process-item-$ITEM
labels:
jobgroup: jobexample
spec:
selector:
matchLabels:
app: jobexample
item: $ITEM
template:
metadata:
name: jobexample
labels:
app: jobexample
item: $ITEM
jobgroup: jobexample
spec:
containers:
- 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
example, the frame number or the row range.
This Job has two labels. The first label, `app=jobexample`, distinguishes this group of jobs from
other groups of jobs (these are not shown, but there might be other ones). This label
makes it convenient to operate on all the jobs in the group at once. The second label, with
key `item`, distinguishes individual jobs in the group. Each Job object needs to have
a unique label that no other job has. This is it.
Neither of these label keys are special to kubernetes -- you can pick your own label scheme.
This Job and its Pod template have a label: `jobgroup=jobexample`. There is nothing special
to the system about this label. This label
makes it convenient to operate on all the jobs in this group at once.
We also put the same label on the pod template so that we can check on all Pods of these Jobs
with a single command.
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.
@ -113,27 +112,25 @@ job "process-item-cherry" created
Now, check on the jobs:
```console
$ kubectl get jobs -l app=jobexample -L item
JOB CONTAINER(S) IMAGE(S) SELECTOR SUCCESSFUL ITEM
process-item-apple c busybox app in (jobexample),item in (apple) 1 apple
process-item-banana c busybox app in (jobexample),item in (banana) 1 banana
process-item-cherry c busybox app in (jobexample),item in (cherry) 1 cherry
$ kubectl get jobs -l app=jobexample
JOB CONTAINER(S) IMAGE(S) SELECTOR SUCCESSFUL
process-item-apple c busybox app in (jobexample),item in (apple) 1
process-item-banana c busybox app in (jobexample),item in (banana) 1
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
group of jobs. (There might be other unrelated jobs in the system that we
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:
```console
$ kubectl get pods -l app=jobexample -L item
NAME READY STATUS RESTARTS AGE ITEM
process-item-apple-kixwv 0/1 Completed 0 4m apple
process-item-banana-wrsf7 0/1 Completed 0 4m banana
process-item-cherry-dnfu9 0/1 Completed 0 4m cherry
$ kubectl get pods -l app=jobexample
NAME READY STATUS RESTARTS AGE
process-item-apple-kixwv 0/1 Completed 0 4m
process-item-banana-wrsf7 0/1 Completed 0 4m
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,
@ -170,21 +167,17 @@ First, download or paste the following template file to a file called `job.yaml.
{%- for p in params %}
{%- set name = p["name"] %}
{%- set url = p["url"] %}
apiVersion: extensions/v1beta1
apiVersion: batch/v1
kind: Job
metadata:
name: jobexample-{{ name }}
labels:
jobgroup: jobexample
spec:
selector:
matchLabels:
app: jobexample
item: {{ name }}
template:
metadata:
name: jobexample
labels:
app: jobexample
item: {{ name }}
jobgroup: jobexample
spec:
containers:
- name: c

View File

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

View File

@ -1,18 +1,15 @@
apiVersion: extensions/v1beta1
apiVersion: batch/v1
kind: Job
metadata:
name: process-item-$ITEM
labels:
jobgroup: jobexample
spec:
selector:
matchLabels:
app: jobexample
item: $ITEM
template:
metadata:
name: jobexample
labels:
app: jobexample
item: $ITEM
jobgroup: jobexample
spec:
containers:
- 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 -->
```yaml
apiVersion: extensions/v1beta1
apiVersion: batch/v1
kind: Job
metadata:
name: job-wq-1
spec:
selector:
matchLabels:
app: job-wq-1
completions: 8
parallelism: 2
template:
metadata:
name: job-wq-1
labels:
app: job-wq-1
spec:
containers:
- name: c

View File

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

View File

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

View File

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

View File

@ -162,6 +162,11 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source)
parallelism := int(c.Rand.Int31())
j.Completions = &completions
j.Parallelism = &parallelism
if c.Rand.Int31()%2 == 0 {
j.ManualSelector = newBool(true)
} else {
j.ManualSelector = nil
}
},
func(j *api.List, c fuzz.Continue) {
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
}
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 {
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 {
return err
}
@ -2885,6 +2891,12 @@ func autoConvert_extensions_JobSpec_To_v1_JobSpec(in *extensions.JobSpec, out *J
} else {
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 {
return err
}

View File

@ -1076,6 +1076,12 @@ func deepCopy_v1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) error {
} else {
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 {
return err
}

View File

@ -23,18 +23,6 @@ import (
func addDefaultingFuncs(scheme *runtime.Scheme) {
scheme.AddDefaultingFuncs(
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
// `.spec.parallelism` unset. When both are unset, both are defaulted to 1.
if obj.Spec.Completions == nil && obj.Spec.Parallelism == nil {

View File

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

View File

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

View File

@ -535,7 +535,10 @@ type JobSpec struct {
Parallelism *int `json:"parallelism,omitempty"`
// 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"`
// 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"`
// 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"`
// 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
// executing a job.
Template api.PodTemplateSpec `json:"template"`

View File

@ -42,6 +42,8 @@ func addConversionFuncs(scheme *runtime.Scheme) {
Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment,
Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec,
Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec,
Convert_extensions_JobSpec_To_v1beta1_JobSpec,
Convert_v1beta1_JobSpec_To_extensions_JobSpec,
)
if err != nil {
// 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
}
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 {
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 {
return err
}
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 {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.JobStatus))(in)
@ -4730,16 +4727,13 @@ func autoConvert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensi
} else {
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 {
return err
}
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 {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
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 {
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 {
return err
}

View File

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

View File

@ -379,9 +379,62 @@ func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) fiel
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 {
// Jobs and rcs have the same name validation
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"))...)
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"))...)
}
// 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 {
labels := labels.Set(spec.Template.Labels)
if !selector.Matches(labels) {

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/controller/podautoscaler"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/intstr"
)
@ -975,12 +976,15 @@ func TestValidateDeploymentRollback(t *testing.T) {
}
func TestValidateJob(t *testing.T) {
validSelector := &unversioned.LabelSelector{
validManualSelector := &unversioned.LabelSelector{
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{
Labels: validSelector.MatchLabels,
Labels: validManualSelector.MatchLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
@ -988,21 +992,45 @@ func TestValidateJob(t *testing.T) {
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{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Selector: validSelector,
Template: validPodTemplateSpec,
Selector: validManualSelector,
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 {
if errs := ValidateJob(&successCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
for k, v := range successCases {
if errs := ValidateJob(&v); len(errs) != 0 {
t.Errorf("expected success for %s: %v", k, errs)
}
}
negative := -1
@ -1012,51 +1040,59 @@ func TestValidateJob(t *testing.T) {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Parallelism: &negative,
Selector: validSelector,
Template: validPodTemplateSpec,
Parallelism: &negative,
ManualSelector: newBool(true),
Template: validPodTemplateSpecForGenerated,
},
},
"spec.completions:must be greater than or equal to 0": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Completions: &negative,
Selector: validSelector,
Template: validPodTemplateSpec,
Completions: &negative,
Selector: validManualSelector,
ManualSelector: newBool(true),
Template: validPodTemplateSpecForGenerated,
},
},
"spec.activeDeadlineSeconds:must be greater than or equal to 0": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
ActiveDeadlineSeconds: &negative64,
Selector: validSelector,
Template: validPodTemplateSpec,
Selector: validManualSelector,
ManualSelector: newBool(true),
Template: validPodTemplateSpecForGenerated,
},
},
"spec.selector:Required value": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Template: validPodTemplateSpec,
Template: validPodTemplateSpecForGenerated,
},
},
"spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Selector: validSelector,
Selector: validManualSelector,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
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": {
ObjectMeta: api.ObjectMeta{
Name: "myjob",
Namespace: api.NamespaceDefault,
UID: types.UID("1a2b3c"),
},
Spec: extensions.JobSpec{
Selector: validSelector,
Selector: validManualSelector,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector.MatchLabels,
Labels: validManualSelector.MatchLabels,
},
Spec: api.PodSpec{
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{
MatchLabels: labels,
},
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: labels,
@ -607,3 +608,9 @@ func parseEnvs(envArray []string) ([]api.EnvVar, error) {
}
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{
MatchLabels: map[string]string{"foo": "bar", "baz": "blah"},
},
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"foo": "bar", "baz": "blah"},

View File

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

View File

@ -21,6 +21,7 @@ import (
"strconv"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields"
@ -60,9 +61,63 @@ func (jobStrategy) PrepareForUpdate(obj, old runtime.Object) {
// Validate validates a new job.
func (jobStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
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)
}
// 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.
func (jobStrategy) Canonicalize(obj runtime.Object) {
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package job
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
@ -25,8 +26,15 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"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) {
ctx := api.NewDefaultContext()
if !Strategy.NamespaceScoped() {
@ -55,8 +63,9 @@ func TestJobStrategy(t *testing.T) {
Namespace: api.NamespaceDefault,
},
Spec: extensions.JobSpec{
Selector: validSelector,
Template: validPodTemplateSpec,
Selector: validSelector,
Template: validPodTemplateSpec,
ManualSelector: newBool(true),
},
Status: extensions.JobStatus{
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) {
ctx := api.NewDefaultContext()
if !StatusStrategy.NamespaceScoped() {

View File

@ -204,8 +204,9 @@ func newTestJob(behavior, name string, rPol api.RestartPolicy, parallelism, comp
Name: name,
},
Spec: extensions.JobSpec{
Parallelism: &parallelism,
Completions: &completions,
Parallelism: &parallelism,
Completions: &completions,
ManualSelector: newBool(true),
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{jobSelectorKey: name},
@ -312,3 +313,9 @@ func waitForJobFail(c *client.Client, ns, jobName string) error {
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",
"apiVersion": "batch/v1",
"metadata": {
"name": "pi",
"labels": {
"app": "pi"
}
"name": "pi"
},
"spec": {
"parallelism": 1,
"completions": 1,
"selector": {
"matchLabels": {
"app": "pi"
}
},
"template": {
"metadata": {
"name": "pi",
"creationTimestamp": null,
"labels": {
"app": "pi"
}
"creationTimestamp": null
},
"spec": {
"containers": [