From 29c50cdc1adbab0ac70f4ebb2874e6de8062601e Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Wed, 14 Oct 2015 11:03:59 -0700 Subject: [PATCH 1/3] plumb PodSelector through the api --- pkg/apis/extensions/helpers.go | 67 +++++++++++++++ pkg/apis/extensions/helpers_test.go | 83 +++++++++++++++++++ pkg/apis/extensions/types.go | 13 +-- pkg/apis/extensions/v1beta1/defaults.go | 6 +- pkg/apis/extensions/v1beta1/defaults_test.go | 8 +- pkg/apis/extensions/v1beta1/types.go | 39 ++++++++- pkg/apis/extensions/validation/validation.go | 45 ++++++++-- .../extensions/validation/validation_test.go | 12 +-- pkg/labels/selector.go | 12 +++ 9 files changed, 262 insertions(+), 23 deletions(-) create mode 100644 pkg/apis/extensions/helpers.go create mode 100644 pkg/apis/extensions/helpers_test.go diff --git a/pkg/apis/extensions/helpers.go b/pkg/apis/extensions/helpers.go new file mode 100644 index 00000000000..23e0abba673 --- /dev/null +++ b/pkg/apis/extensions/helpers.go @@ -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 +} diff --git a/pkg/apis/extensions/helpers_test.go b/pkg/apis/extensions/helpers_test.go new file mode 100644 index 00000000000..866230bfcc7 --- /dev/null +++ b/pkg/apis/extensions/helpers_test.go @@ -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) + } + } +} diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index 4c7f52f895f..9b73b607622 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -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. diff --git a/pkg/apis/extensions/v1beta1/defaults.go b/pkg/apis/extensions/v1beta1/defaults.go index 07c69158075..e1018ff416b 100644 --- a/pkg/apis/extensions/v1beta1/defaults.go +++ b/pkg/apis/extensions/v1beta1/defaults.go @@ -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 diff --git a/pkg/apis/extensions/v1beta1/defaults_test.go b/pkg/apis/extensions/v1beta1/defaults_test.go index 4eb9069b321..baab807924d 100644 --- a/pkg/apis/extensions/v1beta1/defaults_test.go +++ b/pkg/apis/extensions/v1beta1/defaults_test.go @@ -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 diff --git a/pkg/apis/extensions/v1beta1/types.go b/pkg/apis/extensions/v1beta1/types.go index 5b05de54a2f..250c939ba0d 100644 --- a/pkg/apis/extensions/v1beta1/types.go +++ b/pkg/apis/extensions/v1beta1/types.go @@ -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" +) diff --git a/pkg/apis/extensions/validation/validation.go b/pkg/apis/extensions/validation/validation.go index a1b3f96f965..a9ba7d98e01 100644 --- a/pkg/apis/extensions/validation/validation.go +++ b/pkg/apis/extensions/validation/validation.go @@ -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 +} diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index ddbfa0f881b..f1654309432 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -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, diff --git a/pkg/labels/selector.go b/pkg/labels/selector.go index fabf0f1ccd3..8f6a3b5e64e 100644 --- a/pkg/labels/selector.go +++ b/pkg/labels/selector.go @@ -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 "" } +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 From 5f55be5dc1f3171eb92a8f4f723bdf78cdb89a51 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Wed, 14 Oct 2015 11:04:12 -0700 Subject: [PATCH 2/3] autogenerated api changes --- pkg/apis/extensions/deep_copy_generated.go | 44 ++++++- .../v1beta1/conversion_generated.go | 116 +++++++++++++++++- .../extensions/v1beta1/deep_copy_generated.go | 44 ++++++- .../v1beta1/types_swagger_doc_generated.go | 21 ++++ 4 files changed, 213 insertions(+), 12 deletions(-) diff --git a/pkg/apis/extensions/deep_copy_generated.go b/pkg/apis/extensions/deep_copy_generated.go index fcd0c810b6b..ceba7e38320 100644 --- a/pkg/apis/extensions/deep_copy_generated.go +++ b/pkg/apis/extensions/deep_copy_generated.go @@ -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, diff --git a/pkg/apis/extensions/v1beta1/conversion_generated.go b/pkg/apis/extensions/v1beta1/conversion_generated.go index 51465c728bb..e233725ae90 100644 --- a/pkg/apis/extensions/v1beta1/conversion_generated.go +++ b/pkg/apis/extensions/v1beta1/conversion_generated.go @@ -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, diff --git a/pkg/apis/extensions/v1beta1/deep_copy_generated.go b/pkg/apis/extensions/v1beta1/deep_copy_generated.go index a573e9b8ac4..c5fdaa95c6a 100644 --- a/pkg/apis/extensions/v1beta1/deep_copy_generated.go +++ b/pkg/apis/extensions/v1beta1/deep_copy_generated.go @@ -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, diff --git a/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go b/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go index 0e047782d7d..5994aafd3ec 100644 --- a/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go +++ b/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go @@ -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", } From a7a54cac74dd2f7642e0276bb926f9d90aa11f74 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Wed, 14 Oct 2015 11:04:33 -0700 Subject: [PATCH 3/3] move job to generalized label selector --- pkg/client/cache/listers.go | 6 ++---- pkg/controller/job/controller.go | 3 ++- pkg/controller/job/controller_test.go | 20 ++++++++++++++++---- pkg/kubectl/describe.go | 3 ++- pkg/kubectl/resource_printer.go | 4 +++- pkg/kubectl/stop_test.go | 10 ++++++++-- pkg/registry/job/etcd/etcd_test.go | 8 +++++--- pkg/registry/job/strategy_test.go | 12 ++++++++---- 8 files changed, 46 insertions(+), 20 deletions(-) diff --git a/pkg/client/cache/listers.go b/pkg/client/cache/listers.go index 060e74e7f78..12c81b1ae47 100644 --- a/pkg/client/cache/listers.go +++ b/pkg/client/cache/listers.go @@ -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) diff --git a/pkg/controller/job/controller.go b/pkg/controller/job/controller.go index f2f702c3085..1c13cca161c 100644 --- a/pkg/controller/job/controller.go +++ b/pkg/controller/job/controller.go @@ -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) diff --git a/pkg/controller/job/controller_test.go b/pkg/controller/job/controller_test.go index 21f1f177518..25adf5ff858 100644 --- a/pkg/controller/job/controller_test.go +++ b/pkg/controller/job/controller_test.go @@ -43,7 +43,9 @@ func newJob(parallelism, completions int) *extensions.Job { Spec: extensions.JobSpec{ Parallelism: ¶llelism, 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{ diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 8966b0cf618..462d62c9711 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -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)) diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 3937cd9cf7f..e7f83f45e0b 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -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 diff --git a/pkg/kubectl/stop_test.go b/pkg/kubectl/stop_test.go index c640d7712da..d5b645353c6 100644 --- a/pkg/kubectl/stop_test.go +++ b/pkg/kubectl/stop_test.go @@ -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"}, + }, + }, }, }, }, diff --git a/pkg/registry/job/etcd/etcd_test.go b/pkg/registry/job/etcd/etcd_test.go index 399e2547952..19c43e57d01 100644 --- a/pkg/registry/job/etcd/etcd_test.go +++ b/pkg/registry/job/etcd/etcd_test.go @@ -47,7 +47,9 @@ func validNewJob() *extensions.Job { Spec: extensions.JobSpec{ Completions: &completions, Parallelism: ¶llelism, - 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 { diff --git a/pkg/registry/job/strategy_test.go b/pkg/registry/job/strategy_test.go index 6b90786128e..376d5ac76f1 100644 --- a/pkg/registry/job/strategy_test.go +++ b/pkg/registry/job/strategy_test.go @@ -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,