Merge pull request #15520 from mikedanese/dne

Move job to generalized label selector
This commit is contained in:
Eric Tune 2015-10-15 13:59:22 -07:00
commit 13ae9a4d46
21 changed files with 521 additions and 55 deletions

View File

@ -1294,9 +1294,9 @@ func deepCopy_extensions_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner)
out.Completions = nil out.Completions = nil
} }
if in.Selector != nil { if in.Selector != nil {
out.Selector = make(map[string]string) out.Selector = new(PodSelector)
for key, val := range in.Selector { if err := deepCopy_extensions_PodSelector(*in.Selector, out.Selector, c); err != nil {
out.Selector[key] = val return err
} }
} else { } else {
out.Selector = nil out.Selector = nil
@ -1346,6 +1346,42 @@ func deepCopy_extensions_NodeUtilization(in NodeUtilization, out *NodeUtilizatio
return nil return nil
} }
func deepCopy_extensions_PodSelector(in PodSelector, out *PodSelector, c *conversion.Cloner) error {
if in.MatchLabels != nil {
out.MatchLabels = make(map[string]string)
for key, val := range in.MatchLabels {
out.MatchLabels[key] = val
}
} else {
out.MatchLabels = nil
}
if in.MatchExpressions != nil {
out.MatchExpressions = make([]PodSelectorRequirement, len(in.MatchExpressions))
for i := range in.MatchExpressions {
if err := deepCopy_extensions_PodSelectorRequirement(in.MatchExpressions[i], &out.MatchExpressions[i], c); err != nil {
return err
}
}
} else {
out.MatchExpressions = nil
}
return nil
}
func deepCopy_extensions_PodSelectorRequirement(in PodSelectorRequirement, out *PodSelectorRequirement, c *conversion.Cloner) error {
out.Key = in.Key
out.Operator = in.Operator
if in.Values != nil {
out.Values = make([]string, len(in.Values))
for i := range in.Values {
out.Values[i] = in.Values[i]
}
} else {
out.Values = nil
}
return nil
}
func deepCopy_extensions_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { func deepCopy_extensions_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
@ -1581,6 +1617,8 @@ func init() {
deepCopy_extensions_JobSpec, deepCopy_extensions_JobSpec,
deepCopy_extensions_JobStatus, deepCopy_extensions_JobStatus,
deepCopy_extensions_NodeUtilization, deepCopy_extensions_NodeUtilization,
deepCopy_extensions_PodSelector,
deepCopy_extensions_PodSelectorRequirement,
deepCopy_extensions_ReplicationControllerDummy, deepCopy_extensions_ReplicationControllerDummy,
deepCopy_extensions_ResourceConsumption, deepCopy_extensions_ResourceConsumption,
deepCopy_extensions_RollingUpdateDeployment, deepCopy_extensions_RollingUpdateDeployment,

View File

@ -0,0 +1,67 @@
/*
Copyright 2015 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 extensions
import (
"fmt"
"sort"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/sets"
)
// PodSelectorAsSelector converts the PodSelector api type into a struct that implements
// labels.Selector
func PodSelectorAsSelector(ps *PodSelector) (labels.Selector, error) {
if ps == nil {
return labels.Nothing(), nil
}
if len(ps.MatchLabels)+len(ps.MatchExpressions) == 0 {
return labels.Everything(), nil
}
selector := labels.LabelSelector{}
for k, v := range ps.MatchLabels {
req, err := labels.NewRequirement(k, labels.InOperator, sets.NewString(v))
if err != nil {
return nil, err
}
selector = append(selector, *req)
}
for _, expr := range ps.MatchExpressions {
var op labels.Operator
switch expr.Operator {
case PodSelectorOpIn:
op = labels.InOperator
case PodSelectorOpNotIn:
op = labels.NotInOperator
case PodSelectorOpExists:
op = labels.ExistsOperator
case PodSelectorOpDoesNotExist:
op = labels.DoesNotExistOperator
default:
return nil, fmt.Errorf("%q is not a valid pod selector operator", expr.Operator)
}
req, err := labels.NewRequirement(expr.Key, op, sets.NewString(expr.Values...))
if err != nil {
return nil, err
}
selector = append(selector, *req)
}
sort.Sort(labels.ByKey(selector))
return selector, nil
}

View File

@ -0,0 +1,83 @@
/*
Copyright 2015 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 extensions
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/labels"
)
func TestPodSelectorAsSelector(t *testing.T) {
matchLabels := map[string]string{"foo": "bar"}
matchExpressions := []PodSelectorRequirement{{
Key: "baz",
Operator: PodSelectorOpIn,
Values: []string{"qux", "norf"},
}}
mustParse := func(s string) labels.Selector {
out, e := labels.Parse(s)
if e != nil {
panic(e)
}
return out
}
tc := []struct {
in *PodSelector
out labels.Selector
expectErr bool
}{
{in: nil, out: labels.Nothing()},
{in: &PodSelector{}, out: labels.Everything()},
{
in: &PodSelector{MatchLabels: matchLabels},
out: mustParse("foo in (bar)"),
},
{
in: &PodSelector{MatchExpressions: matchExpressions},
out: mustParse("baz in (norf,qux)"),
},
{
in: &PodSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions},
out: mustParse("foo in (bar),baz in (norf,qux)"),
},
{
in: &PodSelector{
MatchExpressions: []PodSelectorRequirement{{
Key: "baz",
Operator: PodSelectorOpExists,
Values: []string{"qux", "norf"},
}},
},
expectErr: true,
},
}
for i, tc := range tc {
out, err := PodSelectorAsSelector(tc.in)
if err == nil && tc.expectErr {
t.Errorf("[%v]expected error but got none.", i)
}
if err != nil && !tc.expectErr {
t.Errorf("[%v]did not expect error but got: %v", i, err)
}
if !reflect.DeepEqual(out, tc.out) {
t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out)
}
}
}

View File

@ -405,7 +405,7 @@ type JobSpec struct {
Completions *int `json:"completions,omitempty"` Completions *int `json:"completions,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.
Selector map[string]string `json:"selector"` Selector *PodSelector `json:"selector,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.
@ -661,7 +661,7 @@ type PodSelector struct {
MatchExpressions []PodSelectorRequirement `json:"matchExpressions,omitempty"` MatchExpressions []PodSelectorRequirement `json:"matchExpressions,omitempty"`
} }
// A pod selector requirement is a selector that contains values, a key and an operator that // A pod selector requirement is a selector that contains values, a key, and an operator that
// relates the key and values. // relates the key and values.
type PodSelectorRequirement struct { type PodSelectorRequirement struct {
// key is the label key that the selector applies to. // key is the label key that the selector applies to.
@ -669,10 +669,11 @@ type PodSelectorRequirement struct {
// operator represents a key's relationship to a set of values. // operator represents a key's relationship to a set of values.
// Valid operators ard In, NotIn, Exists and DoesNotExist. // Valid operators ard In, NotIn, Exists and DoesNotExist.
Operator PodSelectorOperator `json:"operator"` Operator PodSelectorOperator `json:"operator"`
// values is a set of string values. If the operator is In or NotIn, // values is an array of string values. If the operator is In or NotIn,
// the values set must be non-empty. This array is replaced during a // the values array must be non-empty. If the operator is Exists or DoesNotExist,
// strategic merge patch. // the values array must be empty. This array is replaced during a strategic
Values []string `json:"stringValues,omitempty"` // merge patch.
Values []string `json:"values,omitempty"`
} }
// A pod selector operator is the set of operators that can be used in a selector requirement. // A pod selector operator is the set of operators that can be used in a selector requirement.

View File

@ -2785,9 +2785,9 @@ func autoconvert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, o
out.Completions = nil out.Completions = nil
} }
if in.Selector != nil { if in.Selector != nil {
out.Selector = make(map[string]string) out.Selector = new(PodSelector)
for key, val := range in.Selector { if err := convert_extensions_PodSelector_To_v1beta1_PodSelector(in.Selector, out.Selector, s); err != nil {
out.Selector[key] = val return err
} }
} else { } else {
out.Selector = nil out.Selector = nil
@ -2853,6 +2853,56 @@ func convert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(in *extension
return autoconvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(in, out, s) return autoconvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(in, out, s)
} }
func autoconvert_extensions_PodSelector_To_v1beta1_PodSelector(in *extensions.PodSelector, out *PodSelector, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.PodSelector))(in)
}
if in.MatchLabels != nil {
out.MatchLabels = make(map[string]string)
for key, val := range in.MatchLabels {
out.MatchLabels[key] = val
}
} else {
out.MatchLabels = nil
}
if in.MatchExpressions != nil {
out.MatchExpressions = make([]PodSelectorRequirement, len(in.MatchExpressions))
for i := range in.MatchExpressions {
if err := convert_extensions_PodSelectorRequirement_To_v1beta1_PodSelectorRequirement(&in.MatchExpressions[i], &out.MatchExpressions[i], s); err != nil {
return err
}
}
} else {
out.MatchExpressions = nil
}
return nil
}
func convert_extensions_PodSelector_To_v1beta1_PodSelector(in *extensions.PodSelector, out *PodSelector, s conversion.Scope) error {
return autoconvert_extensions_PodSelector_To_v1beta1_PodSelector(in, out, s)
}
func autoconvert_extensions_PodSelectorRequirement_To_v1beta1_PodSelectorRequirement(in *extensions.PodSelectorRequirement, out *PodSelectorRequirement, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.PodSelectorRequirement))(in)
}
out.Key = in.Key
out.Operator = PodSelectorOperator(in.Operator)
if in.Values != nil {
out.Values = make([]string, len(in.Values))
for i := range in.Values {
out.Values[i] = in.Values[i]
}
} else {
out.Values = nil
}
return nil
}
func convert_extensions_PodSelectorRequirement_To_v1beta1_PodSelectorRequirement(in *extensions.PodSelectorRequirement, out *PodSelectorRequirement, s conversion.Scope) error {
return autoconvert_extensions_PodSelectorRequirement_To_v1beta1_PodSelectorRequirement(in, out, s)
}
func autoconvert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControllerDummy(in *extensions.ReplicationControllerDummy, out *ReplicationControllerDummy, s conversion.Scope) error { func autoconvert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControllerDummy(in *extensions.ReplicationControllerDummy, out *ReplicationControllerDummy, 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.ReplicationControllerDummy))(in) defaulting.(func(*extensions.ReplicationControllerDummy))(in)
@ -3702,9 +3752,9 @@ func autoconvert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensi
out.Completions = nil out.Completions = nil
} }
if in.Selector != nil { if in.Selector != nil {
out.Selector = make(map[string]string) out.Selector = new(extensions.PodSelector)
for key, val := range in.Selector { if err := convert_v1beta1_PodSelector_To_extensions_PodSelector(in.Selector, out.Selector, s); err != nil {
out.Selector[key] = val return err
} }
} else { } else {
out.Selector = nil out.Selector = nil
@ -3770,6 +3820,56 @@ func convert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(in *NodeUtili
return autoconvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(in, out, s) return autoconvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(in, out, s)
} }
func autoconvert_v1beta1_PodSelector_To_extensions_PodSelector(in *PodSelector, out *extensions.PodSelector, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*PodSelector))(in)
}
if in.MatchLabels != nil {
out.MatchLabels = make(map[string]string)
for key, val := range in.MatchLabels {
out.MatchLabels[key] = val
}
} else {
out.MatchLabels = nil
}
if in.MatchExpressions != nil {
out.MatchExpressions = make([]extensions.PodSelectorRequirement, len(in.MatchExpressions))
for i := range in.MatchExpressions {
if err := convert_v1beta1_PodSelectorRequirement_To_extensions_PodSelectorRequirement(&in.MatchExpressions[i], &out.MatchExpressions[i], s); err != nil {
return err
}
}
} else {
out.MatchExpressions = nil
}
return nil
}
func convert_v1beta1_PodSelector_To_extensions_PodSelector(in *PodSelector, out *extensions.PodSelector, s conversion.Scope) error {
return autoconvert_v1beta1_PodSelector_To_extensions_PodSelector(in, out, s)
}
func autoconvert_v1beta1_PodSelectorRequirement_To_extensions_PodSelectorRequirement(in *PodSelectorRequirement, out *extensions.PodSelectorRequirement, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*PodSelectorRequirement))(in)
}
out.Key = in.Key
out.Operator = extensions.PodSelectorOperator(in.Operator)
if in.Values != nil {
out.Values = make([]string, len(in.Values))
for i := range in.Values {
out.Values[i] = in.Values[i]
}
} else {
out.Values = nil
}
return nil
}
func convert_v1beta1_PodSelectorRequirement_To_extensions_PodSelectorRequirement(in *PodSelectorRequirement, out *extensions.PodSelectorRequirement, s conversion.Scope) error {
return autoconvert_v1beta1_PodSelectorRequirement_To_extensions_PodSelectorRequirement(in, out, s)
}
func autoconvert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControllerDummy(in *ReplicationControllerDummy, out *extensions.ReplicationControllerDummy, s conversion.Scope) error { func autoconvert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControllerDummy(in *ReplicationControllerDummy, out *extensions.ReplicationControllerDummy, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ReplicationControllerDummy))(in) defaulting.(func(*ReplicationControllerDummy))(in)
@ -4057,6 +4157,8 @@ func init() {
autoconvert_extensions_JobStatus_To_v1beta1_JobStatus, autoconvert_extensions_JobStatus_To_v1beta1_JobStatus,
autoconvert_extensions_Job_To_v1beta1_Job, autoconvert_extensions_Job_To_v1beta1_Job,
autoconvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization, autoconvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization,
autoconvert_extensions_PodSelectorRequirement_To_v1beta1_PodSelectorRequirement,
autoconvert_extensions_PodSelector_To_v1beta1_PodSelector,
autoconvert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControllerDummy, autoconvert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControllerDummy,
autoconvert_extensions_ResourceConsumption_To_v1beta1_ResourceConsumption, autoconvert_extensions_ResourceConsumption_To_v1beta1_ResourceConsumption,
autoconvert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment, autoconvert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment,
@ -4140,6 +4242,8 @@ func init() {
autoconvert_v1beta1_JobStatus_To_extensions_JobStatus, autoconvert_v1beta1_JobStatus_To_extensions_JobStatus,
autoconvert_v1beta1_Job_To_extensions_Job, autoconvert_v1beta1_Job_To_extensions_Job,
autoconvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization, autoconvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization,
autoconvert_v1beta1_PodSelectorRequirement_To_extensions_PodSelectorRequirement,
autoconvert_v1beta1_PodSelector_To_extensions_PodSelector,
autoconvert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControllerDummy, autoconvert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControllerDummy,
autoconvert_v1beta1_ResourceConsumption_To_extensions_ResourceConsumption, autoconvert_v1beta1_ResourceConsumption_To_extensions_ResourceConsumption,
autoconvert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, autoconvert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment,

View File

@ -1306,9 +1306,9 @@ func deepCopy_v1beta1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) er
out.Completions = nil out.Completions = nil
} }
if in.Selector != nil { if in.Selector != nil {
out.Selector = make(map[string]string) out.Selector = new(PodSelector)
for key, val := range in.Selector { if err := deepCopy_v1beta1_PodSelector(*in.Selector, out.Selector, c); err != nil {
out.Selector[key] = val return err
} }
} else { } else {
out.Selector = nil out.Selector = nil
@ -1358,6 +1358,42 @@ func deepCopy_v1beta1_NodeUtilization(in NodeUtilization, out *NodeUtilization,
return nil return nil
} }
func deepCopy_v1beta1_PodSelector(in PodSelector, out *PodSelector, c *conversion.Cloner) error {
if in.MatchLabels != nil {
out.MatchLabels = make(map[string]string)
for key, val := range in.MatchLabels {
out.MatchLabels[key] = val
}
} else {
out.MatchLabels = nil
}
if in.MatchExpressions != nil {
out.MatchExpressions = make([]PodSelectorRequirement, len(in.MatchExpressions))
for i := range in.MatchExpressions {
if err := deepCopy_v1beta1_PodSelectorRequirement(in.MatchExpressions[i], &out.MatchExpressions[i], c); err != nil {
return err
}
}
} else {
out.MatchExpressions = nil
}
return nil
}
func deepCopy_v1beta1_PodSelectorRequirement(in PodSelectorRequirement, out *PodSelectorRequirement, c *conversion.Cloner) error {
out.Key = in.Key
out.Operator = in.Operator
if in.Values != nil {
out.Values = make([]string, len(in.Values))
for i := range in.Values {
out.Values[i] = in.Values[i]
}
} else {
out.Values = nil
}
return nil
}
func deepCopy_v1beta1_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { func deepCopy_v1beta1_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err return err
@ -1603,6 +1639,8 @@ func init() {
deepCopy_v1beta1_JobSpec, deepCopy_v1beta1_JobSpec,
deepCopy_v1beta1_JobStatus, deepCopy_v1beta1_JobStatus,
deepCopy_v1beta1_NodeUtilization, deepCopy_v1beta1_NodeUtilization,
deepCopy_v1beta1_PodSelector,
deepCopy_v1beta1_PodSelectorRequirement,
deepCopy_v1beta1_ReplicationControllerDummy, deepCopy_v1beta1_ReplicationControllerDummy,
deepCopy_v1beta1_ResourceConsumption, deepCopy_v1beta1_ResourceConsumption,
deepCopy_v1beta1_RollingUpdateDeployment, deepCopy_v1beta1_RollingUpdateDeployment,

View File

@ -92,8 +92,10 @@ func addDefaultingFuncs() {
labels := obj.Spec.Template.Labels labels := obj.Spec.Template.Labels
// TODO: support templates defined elsewhere when we support them in the API // TODO: support templates defined elsewhere when we support them in the API
if labels != nil { if labels != nil {
if len(obj.Spec.Selector) == 0 { if obj.Spec.Selector == nil {
obj.Spec.Selector = labels obj.Spec.Selector = &PodSelector{
MatchLabels: labels,
}
} }
if len(obj.Labels) == 0 { if len(obj.Labels) == 0 {
obj.Labels = labels obj.Labels = labels

View File

@ -192,7 +192,9 @@ func TestSetDefaultDeployment(t *testing.T) {
func TestSetDefaultJob(t *testing.T) { func TestSetDefaultJob(t *testing.T) {
expected := &Job{ expected := &Job{
Spec: JobSpec{ Spec: JobSpec{
Selector: map[string]string{"job": "selector"}, Selector: &PodSelector{
MatchLabels: map[string]string{"job": "selector"},
},
Completions: newInt(1), Completions: newInt(1),
Parallelism: newInt(1), Parallelism: newInt(1),
}, },
@ -201,7 +203,9 @@ func TestSetDefaultJob(t *testing.T) {
// selector set explicitly, completions and parallelism - default // selector set explicitly, completions and parallelism - default
{ {
Spec: JobSpec{ Spec: JobSpec{
Selector: map[string]string{"job": "selector"}, Selector: &PodSelector{
MatchLabels: map[string]string{"job": "selector"},
},
}, },
}, },
// selector from template labels, completions and parallelism - default // selector from template labels, completions and parallelism - default

View File

@ -412,7 +412,7 @@ type JobSpec struct {
// 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.
// 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 map[string]string `json:"selector,omitempty"` Selector *PodSelector `json:"selector,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.
@ -657,3 +657,40 @@ type ClusterAutoscalerList struct {
Items []ClusterAutoscaler `json:"items"` Items []ClusterAutoscaler `json:"items"`
} }
// A pod selector is a label query over a set of pods. The result of matchLabels and
// matchExpressions are ANDed. An empty pod selector matches all objects. A null
// pod selector matches no objects.
type PodSelector struct {
// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
// map is equivalent to an element of matchExpressions, whose key field is "key", the
// operator is "In", and the values array contains only "value". The requirements are ANDed.
MatchLabels map[string]string `json:"matchLabels,omitempty"`
// matchExpressions is a list of pod selector requirements. The requirements are ANDed.
MatchExpressions []PodSelectorRequirement `json:"matchExpressions,omitempty"`
}
// A pod selector requirement is a selector that contains values, a key, and an operator that
// relates the key and values.
type PodSelectorRequirement struct {
// key is the label key that the selector applies to.
Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key"`
// operator represents a key's relationship to a set of values.
// Valid operators ard In, NotIn, Exists and DoesNotExist.
Operator PodSelectorOperator `json:"operator"`
// values is an array of string values. If the operator is In or NotIn,
// the values array must be non-empty. If the operator is Exists or DoesNotExist,
// the values array must be empty. This array is replaced during a strategic
// merge patch.
Values []string `json:"values,omitempty"`
}
// A pod selector operator is the set of operators that can be used in a selector requirement.
type PodSelectorOperator string
const (
PodSelectorOpIn PodSelectorOperator = "In"
PodSelectorOpNotIn PodSelectorOperator = "NotIn"
PodSelectorOpExists PodSelectorOperator = "Exists"
PodSelectorOpDoesNotExist PodSelectorOperator = "DoesNotExist"
)

View File

@ -363,6 +363,27 @@ func (NodeUtilization) SwaggerDoc() map[string]string {
return map_NodeUtilization return map_NodeUtilization
} }
var map_PodSelector = map[string]string{
"": "A pod selector is a label query over a set of pods. The result of matchLabels and matchExpressions are ANDed. An empty pod selector matches all objects. A null pod selector matches no objects.",
"matchLabels": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
"matchExpressions": "matchExpressions is a list of pod selector requirements. The requirements are ANDed.",
}
func (PodSelector) SwaggerDoc() map[string]string {
return map_PodSelector
}
var map_PodSelectorRequirement = map[string]string{
"": "A pod selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
"key": "key is the label key that the selector applies to.",
"operator": "operator represents a key's relationship to a set of values. Valid operators ard In, NotIn, Exists and DoesNotExist.",
"values": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.",
}
func (PodSelectorRequirement) SwaggerDoc() map[string]string {
return map_PodSelectorRequirement
}
var map_ReplicationControllerDummy = map[string]string{ var map_ReplicationControllerDummy = map[string]string{
"": "Dummy definition", "": "Dummy definition",
} }

View File

@ -345,16 +345,19 @@ func ValidateJobSpec(spec *extensions.JobSpec) errs.ValidationErrorList {
if spec.Completions != nil && *spec.Completions < 0 { if spec.Completions != nil && *spec.Completions < 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("completions", spec.Completions, isNegativeErrorMsg)) allErrs = append(allErrs, errs.NewFieldInvalid("completions", spec.Completions, isNegativeErrorMsg))
} }
if spec.Selector == nil {
selector := labels.Set(spec.Selector).AsSelector()
if selector.Empty() {
allErrs = append(allErrs, errs.NewFieldRequired("selector")) allErrs = append(allErrs, errs.NewFieldRequired("selector"))
} else {
allErrs = append(allErrs, ValidatePodSelector(spec.Selector).Prefix("selector")...)
} }
labels := labels.Set(spec.Template.Labels) if selector, err := extensions.PodSelectorAsSelector(spec.Selector); err == nil {
if !selector.Matches(labels) { labels := labels.Set(spec.Template.Labels)
allErrs = append(allErrs, errs.NewFieldInvalid("template.labels", spec.Template.Labels, "selector does not match template")) if !selector.Matches(labels) {
allErrs = append(allErrs, errs.NewFieldInvalid("template.metadata.labels", spec.Template.Labels, "selector does not match template"))
}
} }
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template).Prefix("template")...) allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template).Prefix("template")...)
if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
@ -568,3 +571,33 @@ func ValidateClusterAutoscaler(autoscaler *extensions.ClusterAutoscaler) errs.Va
allErrs = append(allErrs, validateClusterAutoscalerSpec(autoscaler.Spec)...) allErrs = append(allErrs, validateClusterAutoscalerSpec(autoscaler.Spec)...)
return allErrs return allErrs
} }
func ValidatePodSelector(ps *extensions.PodSelector) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if ps == nil {
return allErrs
}
allErrs = append(allErrs, apivalidation.ValidateLabels(ps.MatchLabels, "matchLabels")...)
for i, expr := range ps.MatchExpressions {
allErrs = append(allErrs, ValidatePodSelectorRequirement(expr).Prefix(fmt.Sprintf("matchExpressions.[%v]", i))...)
}
return allErrs
}
func ValidatePodSelectorRequirement(sr extensions.PodSelectorRequirement) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
switch sr.Operator {
case extensions.PodSelectorOpIn, extensions.PodSelectorOpNotIn:
if len(sr.Values) == 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("values", sr.Values, "must be non-empty when operator is In or NotIn"))
}
case extensions.PodSelectorOpExists, extensions.PodSelectorOpDoesNotExist:
if len(sr.Values) > 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("values", sr.Values, "must be empty when operator is Exists or DoesNotExist"))
}
default:
allErrs = append(allErrs, errs.NewFieldInvalid("operator", sr.Operator, "not a valid pod selector operator"))
}
allErrs = append(allErrs, apivalidation.ValidateLabelName(sr.Key, "key")...)
return allErrs
}

View File

@ -192,7 +192,6 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) {
t.Errorf("expected failure: %s", testName) t.Errorf("expected failure: %s", testName)
} }
} }
} }
func TestValidateDaemonSetUpdate(t *testing.T) { func TestValidateDaemonSetUpdate(t *testing.T) {
@ -725,10 +724,12 @@ func TestValidateDeployment(t *testing.T) {
} }
func TestValidateJob(t *testing.T) { func TestValidateJob(t *testing.T) {
validSelector := map[string]string{"a": "b"} validSelector := &extensions.PodSelector{
MatchLabels: map[string]string{"a": "b"},
}
validPodTemplateSpec := api.PodTemplateSpec{ validPodTemplateSpec := api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector.MatchLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure, RestartPolicy: api.RestartPolicyOnFailure,
@ -783,11 +784,10 @@ func TestValidateJob(t *testing.T) {
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: map[string]string{},
Template: validPodTemplateSpec, Template: validPodTemplateSpec,
}, },
}, },
"spec.template.labels:selector does not match template": { "spec.template.metadata.labels: invalid value 'map[y:z]', Details: selector does not match template": {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "myjob", Name: "myjob",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
@ -815,7 +815,7 @@ func TestValidateJob(t *testing.T) {
Selector: validSelector, Selector: validSelector,
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector.MatchLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways, RestartPolicy: api.RestartPolicyAlways,

View File

@ -382,11 +382,9 @@ func (s *StoreToJobLister) GetPodJobs(pod *api.Pod) (jobs []extensions.Job, err
if job.Namespace != pod.Namespace { if job.Namespace != pod.Namespace {
continue continue
} }
labelSet := labels.Set(job.Spec.Selector)
selector = labels.Set(job.Spec.Selector).AsSelector()
// Job with a nil or empty selector match nothing selector, _ = extensions.PodSelectorAsSelector(job.Spec.Selector)
if labelSet.AsSelector().Empty() || !selector.Matches(labels.Set(pod.Labels)) { if !selector.Matches(labels.Set(pod.Labels)) {
continue continue
} }
jobs = append(jobs, job) jobs = append(jobs, job)

View File

@ -313,7 +313,8 @@ func (jm *JobController) syncJob(key string) error {
return err return err
} }
jobNeedsSync := jm.expectations.SatisfiedExpectations(jobKey) jobNeedsSync := jm.expectations.SatisfiedExpectations(jobKey)
podList, err := jm.podStore.Pods(job.Namespace).List(labels.Set(job.Spec.Selector).AsSelector()) selector, _ := extensions.PodSelectorAsSelector(job.Spec.Selector)
podList, err := jm.podStore.Pods(job.Namespace).List(selector)
if err != nil { if err != nil {
glog.Errorf("Error getting pods for job %q: %v", key, err) glog.Errorf("Error getting pods for job %q: %v", key, err)
jm.queue.Add(key) jm.queue.Add(key)

View File

@ -43,7 +43,9 @@ func newJob(parallelism, completions int) *extensions.Job {
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Parallelism: &parallelism, Parallelism: &parallelism,
Completions: &completions, Completions: &completions,
Selector: map[string]string{"foo": "bar"}, Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{ Labels: map[string]string{
@ -76,7 +78,7 @@ func newPodList(count int, status api.PodPhase, job *extensions.Job) []api.Pod {
newPod := api.Pod{ newPod := api.Pod{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: fmt.Sprintf("pod-%v", unversioned.Now().UnixNano()), Name: fmt.Sprintf("pod-%v", unversioned.Now().UnixNano()),
Labels: job.Spec.Selector, Labels: job.Spec.Selector.MatchLabels,
Namespace: job.Namespace, Namespace: job.Namespace,
}, },
Status: api.PodStatus{Phase: status}, Status: api.PodStatus{Phase: status},
@ -289,7 +291,9 @@ func TestJobPodLookup(t *testing.T) {
job: &extensions.Job{ job: &extensions.Job{
ObjectMeta: api.ObjectMeta{Name: "foo"}, ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: map[string]string{"foo": "bar"}, Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
}, },
}, },
pod: &api.Pod{ pod: &api.Pod{
@ -306,7 +310,15 @@ func TestJobPodLookup(t *testing.T) {
job: &extensions.Job{ job: &extensions.Job{
ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"},
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Selector: map[string]string{"foo": "bar"}, Selector: &extensions.PodSelector{
MatchExpressions: []extensions.PodSelectorRequirement{
{
Key: "foo",
Operator: extensions.PodSelectorOpIn,
Values: []string{"bar"},
},
},
},
}, },
}, },
pod: &api.Pod{ pod: &api.Pod{

View File

@ -885,7 +885,8 @@ func describeJob(job *extensions.Job, events *api.EventList) (string, error) {
fmt.Fprintf(out, "Name:\t%s\n", job.Name) fmt.Fprintf(out, "Name:\t%s\n", job.Name)
fmt.Fprintf(out, "Namespace:\t%s\n", job.Namespace) fmt.Fprintf(out, "Namespace:\t%s\n", job.Namespace)
fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&job.Spec.Template.Spec)) fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&job.Spec.Template.Spec))
fmt.Fprintf(out, "Selector:\t%s\n", labels.FormatLabels(job.Spec.Selector)) selector, _ := extensions.PodSelectorAsSelector(job.Spec.Selector)
fmt.Fprintf(out, "Selector:\t%s\n", selector)
fmt.Fprintf(out, "Parallelism:\t%d\n", *job.Spec.Parallelism) fmt.Fprintf(out, "Parallelism:\t%d\n", *job.Spec.Parallelism)
fmt.Fprintf(out, "Completions:\t%d\n", *job.Spec.Completions) fmt.Fprintf(out, "Completions:\t%d\n", *job.Spec.Completions)
fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(job.Labels)) fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(job.Labels))

View File

@ -736,11 +736,13 @@ func printJob(job *extensions.Job, w io.Writer, withNamespace bool, wide bool, s
return err return err
} }
} }
selector, _ := extensions.PodSelectorAsSelector(job.Spec.Selector)
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n",
name, name,
firstContainer.Name, firstContainer.Name,
firstContainer.Image, firstContainer.Image,
labels.FormatLabels(job.Spec.Selector), selector.String(),
job.Status.Succeeded) job.Status.Succeeded)
if err != nil { if err != nil {
return err return err

View File

@ -293,7 +293,10 @@ func TestJobStop(t *testing.T) {
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Parallelism: &zero, Parallelism: &zero,
Selector: map[string]string{"k1": "v1"}}, Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"k1": "v1"},
},
},
}, },
&extensions.JobList{ // LIST &extensions.JobList{ // LIST
Items: []extensions.Job{ Items: []extensions.Job{
@ -304,7 +307,10 @@ func TestJobStop(t *testing.T) {
}, },
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Parallelism: &zero, Parallelism: &zero,
Selector: map[string]string{"k1": "v1"}}, Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"k1": "v1"},
},
},
}, },
}, },
}, },

View File

@ -47,6 +47,18 @@ func Everything() Selector {
return LabelSelector{} return LabelSelector{}
} }
type nothingSelector struct{}
func (n nothingSelector) Matches(_ Labels) bool { return false }
func (n nothingSelector) Empty() bool { return false }
func (n nothingSelector) String() string { return "<null>" }
func (n nothingSelector) Add(_ string, _ Operator, _ []string) Selector { return n }
// Nothing returns a selector that matches no labels
func Nothing() Selector {
return nothingSelector{}
}
// Operator represents a key's relationship // Operator represents a key's relationship
// to a set of values in a Requirement. // to a set of values in a Requirement.
type Operator string type Operator string

View File

@ -47,7 +47,9 @@ func validNewJob() *extensions.Job {
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Completions: &completions, Completions: &completions,
Parallelism: &parallelism, Parallelism: &parallelism,
Selector: map[string]string{"a": "b"}, Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"a": "b"},
},
Template: api.PodTemplateSpec{ Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"}, Labels: map[string]string{"a": "b"},
@ -80,7 +82,7 @@ func TestCreate(t *testing.T) {
&extensions.Job{ &extensions.Job{
Spec: extensions.JobSpec{ Spec: extensions.JobSpec{
Completions: validJob.Spec.Completions, Completions: validJob.Spec.Completions,
Selector: map[string]string{}, Selector: &extensions.PodSelector{},
Template: validJob.Spec.Template, Template: validJob.Spec.Template,
}, },
}, },
@ -103,7 +105,7 @@ func TestUpdate(t *testing.T) {
// invalid updateFunc // invalid updateFunc
func(obj runtime.Object) runtime.Object { func(obj runtime.Object) runtime.Object {
object := obj.(*extensions.Job) object := obj.(*extensions.Job)
object.Spec.Selector = map[string]string{} object.Spec.Selector = &extensions.PodSelector{}
return object return object
}, },
func(obj runtime.Object) runtime.Object { func(obj runtime.Object) runtime.Object {

View File

@ -32,10 +32,12 @@ func TestJobStrategy(t *testing.T) {
t.Errorf("Job should not allow create on update") t.Errorf("Job should not allow create on update")
} }
validSelector := map[string]string{"a": "b"} validSelector := &extensions.PodSelector{
MatchLabels: map[string]string{"a": "b"},
}
validPodTemplateSpec := api.PodTemplateSpec{ validPodTemplateSpec := api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector.MatchLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure, RestartPolicy: api.RestartPolicyOnFailure,
@ -95,10 +97,12 @@ func TestJobStatusStrategy(t *testing.T) {
if StatusStrategy.AllowCreateOnUpdate() { if StatusStrategy.AllowCreateOnUpdate() {
t.Errorf("Job should not allow create on update") t.Errorf("Job should not allow create on update")
} }
validSelector := map[string]string{"a": "b"} validSelector := &extensions.PodSelector{
MatchLabels: map[string]string{"a": "b"},
}
validPodTemplateSpec := api.PodTemplateSpec{ validPodTemplateSpec := api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Labels: validSelector, Labels: validSelector.MatchLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure, RestartPolicy: api.RestartPolicyOnFailure,