Merge pull request #15520 from mikedanese/dne

Move job to generalized label selector
pull/6/head
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
}
if in.Selector != nil {
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
out.Selector = new(PodSelector)
if err := deepCopy_extensions_PodSelector(*in.Selector, out.Selector, c); err != nil {
return err
}
} else {
out.Selector = nil
@ -1346,6 +1346,42 @@ func deepCopy_extensions_NodeUtilization(in NodeUtilization, out *NodeUtilizatio
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 {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
@ -1581,6 +1617,8 @@ func init() {
deepCopy_extensions_JobSpec,
deepCopy_extensions_JobStatus,
deepCopy_extensions_NodeUtilization,
deepCopy_extensions_PodSelector,
deepCopy_extensions_PodSelectorRequirement,
deepCopy_extensions_ReplicationControllerDummy,
deepCopy_extensions_ResourceConsumption,
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"`
// 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
// executing a job.
@ -661,7 +661,7 @@ type PodSelector struct {
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.
type PodSelectorRequirement struct {
// 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.
// Valid operators ard In, NotIn, Exists and DoesNotExist.
Operator PodSelectorOperator `json:"operator"`
// values is a set of string values. If the operator is In or NotIn,
// the values set must be non-empty. This array is replaced during a
// strategic merge patch.
Values []string `json:"stringValues,omitempty"`
// 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.

View File

@ -2785,9 +2785,9 @@ func autoconvert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, o
out.Completions = nil
}
if in.Selector != nil {
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
out.Selector = new(PodSelector)
if err := convert_extensions_PodSelector_To_v1beta1_PodSelector(in.Selector, out.Selector, s); err != nil {
return err
}
} else {
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)
}
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 {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.ReplicationControllerDummy))(in)
@ -3702,9 +3752,9 @@ func autoconvert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensi
out.Completions = nil
}
if in.Selector != nil {
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
out.Selector = new(extensions.PodSelector)
if err := convert_v1beta1_PodSelector_To_extensions_PodSelector(in.Selector, out.Selector, s); err != nil {
return err
}
} else {
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)
}
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 {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ReplicationControllerDummy))(in)
@ -4057,6 +4157,8 @@ func init() {
autoconvert_extensions_JobStatus_To_v1beta1_JobStatus,
autoconvert_extensions_Job_To_v1beta1_Job,
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_ResourceConsumption_To_v1beta1_ResourceConsumption,
autoconvert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment,
@ -4140,6 +4242,8 @@ func init() {
autoconvert_v1beta1_JobStatus_To_extensions_JobStatus,
autoconvert_v1beta1_Job_To_extensions_Job,
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_ResourceConsumption_To_extensions_ResourceConsumption,
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
}
if in.Selector != nil {
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
out.Selector = new(PodSelector)
if err := deepCopy_v1beta1_PodSelector(*in.Selector, out.Selector, c); err != nil {
return err
}
} else {
out.Selector = nil
@ -1358,6 +1358,42 @@ func deepCopy_v1beta1_NodeUtilization(in NodeUtilization, out *NodeUtilization,
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 {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
@ -1603,6 +1639,8 @@ func init() {
deepCopy_v1beta1_JobSpec,
deepCopy_v1beta1_JobStatus,
deepCopy_v1beta1_NodeUtilization,
deepCopy_v1beta1_PodSelector,
deepCopy_v1beta1_PodSelectorRequirement,
deepCopy_v1beta1_ReplicationControllerDummy,
deepCopy_v1beta1_ResourceConsumption,
deepCopy_v1beta1_RollingUpdateDeployment,

View File

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

View File

@ -192,7 +192,9 @@ func TestSetDefaultDeployment(t *testing.T) {
func TestSetDefaultJob(t *testing.T) {
expected := &Job{
Spec: JobSpec{
Selector: map[string]string{"job": "selector"},
Selector: &PodSelector{
MatchLabels: map[string]string{"job": "selector"},
},
Completions: newInt(1),
Parallelism: newInt(1),
},
@ -201,7 +203,9 @@ func TestSetDefaultJob(t *testing.T) {
// selector set explicitly, completions and parallelism - default
{
Spec: JobSpec{
Selector: map[string]string{"job": "selector"},
Selector: &PodSelector{
MatchLabels: map[string]string{"job": "selector"},
},
},
},
// 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.
// 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
// executing a job.
@ -657,3 +657,40 @@ type ClusterAutoscalerList struct {
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
}
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{
"": "Dummy definition",
}

View File

@ -345,16 +345,19 @@ func ValidateJobSpec(spec *extensions.JobSpec) errs.ValidationErrorList {
if spec.Completions != nil && *spec.Completions < 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("completions", spec.Completions, isNegativeErrorMsg))
}
selector := labels.Set(spec.Selector).AsSelector()
if selector.Empty() {
if spec.Selector == nil {
allErrs = append(allErrs, errs.NewFieldRequired("selector"))
} else {
allErrs = append(allErrs, ValidatePodSelector(spec.Selector).Prefix("selector")...)
}
labels := labels.Set(spec.Template.Labels)
if !selector.Matches(labels) {
allErrs = append(allErrs, errs.NewFieldInvalid("template.labels", spec.Template.Labels, "selector does not match template"))
if selector, err := extensions.PodSelectorAsSelector(spec.Selector); err == nil {
labels := labels.Set(spec.Template.Labels)
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")...)
if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
@ -568,3 +571,33 @@ func ValidateClusterAutoscaler(autoscaler *extensions.ClusterAutoscaler) errs.Va
allErrs = append(allErrs, validateClusterAutoscalerSpec(autoscaler.Spec)...)
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)
}
}
}
func TestValidateDaemonSetUpdate(t *testing.T) {
@ -725,10 +724,12 @@ func TestValidateDeployment(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{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
Labels: validSelector.MatchLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
@ -783,11 +784,10 @@ func TestValidateJob(t *testing.T) {
Namespace: api.NamespaceDefault,
},
Spec: extensions.JobSpec{
Selector: map[string]string{},
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{
Name: "myjob",
Namespace: api.NamespaceDefault,
@ -815,7 +815,7 @@ func TestValidateJob(t *testing.T) {
Selector: validSelector,
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
Labels: validSelector.MatchLabels,
},
Spec: api.PodSpec{
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 {
continue
}
labelSet := labels.Set(job.Spec.Selector)
selector = labels.Set(job.Spec.Selector).AsSelector()
// Job with a nil or empty selector match nothing
if labelSet.AsSelector().Empty() || !selector.Matches(labels.Set(pod.Labels)) {
selector, _ = extensions.PodSelectorAsSelector(job.Spec.Selector)
if !selector.Matches(labels.Set(pod.Labels)) {
continue
}
jobs = append(jobs, job)

View File

@ -313,7 +313,8 @@ func (jm *JobController) syncJob(key string) error {
return err
}
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 {
glog.Errorf("Error getting pods for job %q: %v", key, err)
jm.queue.Add(key)

View File

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

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, "Namespace:\t%s\n", job.Namespace)
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, "Completions:\t%d\n", *job.Spec.Completions)
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
}
}
selector, _ := extensions.PodSelectorAsSelector(job.Spec.Selector)
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n",
name,
firstContainer.Name,
firstContainer.Image,
labels.FormatLabels(job.Spec.Selector),
selector.String(),
job.Status.Succeeded)
if err != nil {
return err

View File

@ -293,7 +293,10 @@ func TestJobStop(t *testing.T) {
},
Spec: extensions.JobSpec{
Parallelism: &zero,
Selector: map[string]string{"k1": "v1"}},
Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"k1": "v1"},
},
},
},
&extensions.JobList{ // LIST
Items: []extensions.Job{
@ -304,7 +307,10 @@ func TestJobStop(t *testing.T) {
},
Spec: extensions.JobSpec{
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{}
}
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
// to a set of values in a Requirement.
type Operator string

View File

@ -47,7 +47,9 @@ func validNewJob() *extensions.Job {
Spec: extensions.JobSpec{
Completions: &completions,
Parallelism: &parallelism,
Selector: map[string]string{"a": "b"},
Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"a": "b"},
},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
@ -80,7 +82,7 @@ func TestCreate(t *testing.T) {
&extensions.Job{
Spec: extensions.JobSpec{
Completions: validJob.Spec.Completions,
Selector: map[string]string{},
Selector: &extensions.PodSelector{},
Template: validJob.Spec.Template,
},
},
@ -103,7 +105,7 @@ func TestUpdate(t *testing.T) {
// invalid updateFunc
func(obj runtime.Object) runtime.Object {
object := obj.(*extensions.Job)
object.Spec.Selector = map[string]string{}
object.Spec.Selector = &extensions.PodSelector{}
return 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")
}
validSelector := map[string]string{"a": "b"}
validSelector := &extensions.PodSelector{
MatchLabels: map[string]string{"a": "b"},
}
validPodTemplateSpec := api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
Labels: validSelector.MatchLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
@ -95,10 +97,12 @@ func TestJobStatusStrategy(t *testing.T) {
if StatusStrategy.AllowCreateOnUpdate() {
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{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
Labels: validSelector.MatchLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,