plumb PodSelector through the api

This commit is contained in:
Mike Danese 2015-10-14 11:03:59 -07:00
parent 407f9b9e42
commit 29c50cdc1a
9 changed files with 262 additions and 23 deletions

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

@ -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

@ -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

@ -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