Skip to content
Permalink
Browse files

Change pod.spec.NodeSelector to full label selector

This will enable user to take advantage of exists,
in, notin, != operators for filtering nodes.
  • Loading branch information...
Ravi Sankar Penta
Ravi Sankar Penta committed Jun 26, 2015
1 parent c694093 commit e8be6643db7e0135bb1bd068afe407336d2067e3
@@ -12350,7 +12350,7 @@
"description": "DNS policy for containers within the pod; one of 'ClusterFirst' or 'Default'"
},
"nodeSelector": {
"type": "any",
"type": "string",
"description": "selector which must match a node's labels for the pod to be scheduled on that node"
},
"serviceAccountName": {
@@ -1299,14 +1299,7 @@ func deepCopy_api_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error
out.ActiveDeadlineSeconds = nil
}
out.DNSPolicy = in.DNSPolicy
if in.NodeSelector != nil {
out.NodeSelector = make(map[string]string)
for key, val := range in.NodeSelector {
out.NodeSelector[key] = val
}
} else {
out.NodeSelector = nil
}
out.NodeSelector = in.NodeSelector
out.ServiceAccountName = in.ServiceAccountName
out.NodeName = in.NodeName
out.HostNetwork = in.HostNetwork
@@ -902,7 +902,7 @@ type PodSpec struct {
// Required: Set DNS policy.
DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty"`
// NodeSelector is a selector which must be true for the pod to fit on a node
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
NodeSelector string `json:"nodeSelector,omitempty"`

// ServiceAccountName is the name of the ServiceAccount to use to run this pod
// The pod will be allowed to use secrets referenced by the ServiceAccount
@@ -1510,14 +1510,7 @@ func convert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *PodSpec, s conversi
out.ActiveDeadlineSeconds = nil
}
out.DNSPolicy = DNSPolicy(in.DNSPolicy)
if in.NodeSelector != nil {
out.NodeSelector = make(map[string]string)
for key, val := range in.NodeSelector {
out.NodeSelector[key] = val
}
} else {
out.NodeSelector = nil
}
out.NodeSelector = in.NodeSelector
out.ServiceAccountName = in.ServiceAccountName
out.NodeName = in.NodeName
out.HostNetwork = in.HostNetwork
@@ -3822,14 +3815,7 @@ func convert_v1_PodSpec_To_api_PodSpec(in *PodSpec, out *api.PodSpec, s conversi
out.ActiveDeadlineSeconds = nil
}
out.DNSPolicy = api.DNSPolicy(in.DNSPolicy)
if in.NodeSelector != nil {
out.NodeSelector = make(map[string]string)
for key, val := range in.NodeSelector {
out.NodeSelector[key] = val
}
} else {
out.NodeSelector = nil
}
out.NodeSelector = in.NodeSelector
out.ServiceAccountName = in.ServiceAccountName
out.NodeName = in.NodeName
out.HostNetwork = in.HostNetwork
@@ -1302,14 +1302,7 @@ func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error {
out.ActiveDeadlineSeconds = nil
}
out.DNSPolicy = in.DNSPolicy
if in.NodeSelector != nil {
out.NodeSelector = make(map[string]string)
for key, val := range in.NodeSelector {
out.NodeSelector[key] = val
}
} else {
out.NodeSelector = nil
}
out.NodeSelector = in.NodeSelector
out.ServiceAccountName = in.ServiceAccountName
out.NodeName = in.NodeName
out.HostNetwork = in.HostNetwork
@@ -873,7 +873,7 @@ type PodSpec struct {
// Optional: Set DNS policy. Defaults to "ClusterFirst"
DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty" description:"DNS policy for containers within the pod; one of 'ClusterFirst' or 'Default'"`
// NodeSelector is a selector which must be true for the pod to fit on a node
NodeSelector map[string]string `json:"nodeSelector,omitempty" description:"selector which must match a node's labels for the pod to be scheduled on that node"`
NodeSelector string `json:"nodeSelector,omitempty" description:"selector which must match a node's labels for the pod to be scheduled on that node"`

// ServiceAccountName is the name of the ServiceAccount to use to run this pod
ServiceAccountName string `json:"serviceAccountName,omitempty" description:"name of the ServiceAccount to use to run this pod"`
@@ -19,6 +19,8 @@ package v1beta3
import (
"fmt"
"reflect"
"regexp"
"strings"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
@@ -486,12 +488,10 @@ func convert_v1beta3_PodSpec_To_api_PodSpec(in *PodSpec, out *api.PodSpec, s con
}
out.DNSPolicy = api.DNSPolicy(in.DNSPolicy)
if in.NodeSelector != nil {
out.NodeSelector = make(map[string]string)
for key, val := range in.NodeSelector {
out.NodeSelector[key] = val
out.NodeSelector += fmt.Sprintf("%s=%s,", key, val)
}
} else {
out.NodeSelector = nil
out.NodeSelector = out.NodeSelector[:len(out.NodeSelector)-1]
}
out.ServiceAccountName = in.ServiceAccount
out.NodeName = in.Host
@@ -547,10 +547,17 @@ func convert_api_PodSpec_To_v1beta3_PodSpec(in *api.PodSpec, out *PodSpec, s con
out.ActiveDeadlineSeconds = nil
}
out.DNSPolicy = DNSPolicy(in.DNSPolicy)
if in.NodeSelector != nil {
if len(in.NodeSelector) > 0 {
out.NodeSelector = make(map[string]string)
for key, val := range in.NodeSelector {
out.NodeSelector[key] = val
labelStrings := strings.Split(in.NodeSelector, ",")
labelPattern := regexp.MustCompile("^\\s*(\\S+)\\s*=\\s*(\\S+)\\s*$")
for _, ls := range labelStrings {
matches := labelPattern.FindStringSubmatch(ls)
if len(matches) != 3 {
return fmt.Errorf("failed to convert pod NodeSelector: %s, v1beta3 only supports key=value label selector", in.NodeSelector)
} else {
out.NodeSelector[matches[1]] = matches[2]
}
}
} else {
out.NodeSelector = nil
@@ -67,6 +67,15 @@ func ValidateLabels(labels map[string]string, field string) errs.ValidationError
return allErrs
}

// ValidateSelector validates label selector.
func ValidateSelector(selector string, field string) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if _, err := labels.Parse(selector); err != nil {
allErrs = append(allErrs, errs.NewFieldInvalid(field, selector, "must be a valid label selector"))
}
return allErrs
}

// ValidateAnnotations validates that a set of annotations are correctly defined.
func ValidateAnnotations(annotations map[string]string, field string) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
@@ -947,7 +956,7 @@ func ValidatePodSpec(spec *api.PodSpec) errs.ValidationErrorList {
allErrs = append(allErrs, validateContainers(spec.Containers, allVolumes).Prefix("containers")...)
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy).Prefix("restartPolicy")...)
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy).Prefix("dnsPolicy")...)
allErrs = append(allErrs, ValidateLabels(spec.NodeSelector, "nodeSelector")...)
allErrs = append(allErrs, ValidateSelector(spec.NodeSelector, "nodeSelector")...)
allErrs = append(allErrs, validateHostNetwork(spec.HostNetwork, spec.Containers).Prefix("hostNetwork")...)
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets).Prefix("imagePullSecrets")...)
if len(spec.ServiceAccountName) > 0 {
@@ -173,6 +173,50 @@ func TestValidateLabels(t *testing.T) {
}
}

func TestValidateSelector(t *testing.T) {
successCases := []string{
"",
"simple=bar",
"now-with-dashes=bar",
"1-starts-with-num=bar",
"1234=bar",
"simple/simple=bar",
"now-with.dashes-and.dots/simple=bar",
"1-num.2-num/3-num=bar",
"1234/5678=bar",
"1.2.3.4/5678=bar",
"UpperCaseAreOK123=bar",
"goodvalue=123_-.BaR",
" check-spaces = bar ,multipleLabels == true ",
"check-not-equal!=bar, foo=Bar",
"check-in-op in (foo,bar)",
"check-notin notin (x,y)",
"check-exists-op, bar",
}
for i := range successCases {
errs := ValidateSelector(successCases[i], "field")
if len(errs) != 0 {
t.Errorf("case[%d] expected success, got %#v", i, errs)
}
}

errorCases := []string{
"nospecialchars^=@ = bar",
"only/one/slash=bar",
"strangecharsinvalue= ?#$notsogood",
"two-equalities==a==b",
"or-operator=a||y=b",
"x nott in (y)",
"x ,, y",
}
for i := range errorCases {
errs := ValidateSelector(errorCases[i], "field")
if len(errs) == 0 {
t.Errorf("case[%d] expected failure", i)
}
}
}

func TestValidateAnnotations(t *testing.T) {
successCases := []map[string]string{
{"simple": "bar"},
@@ -1045,11 +1089,9 @@ func TestValidatePodSpec(t *testing.T) {
Volumes: []api.Volume{
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: map[string]string{
"key": "value",
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: "key=value",
NodeName: "foobar",
DNSPolicy: api.DNSClusterFirst,
ActiveDeadlineSeconds: &activeDeadlineSeconds,
@@ -1119,11 +1161,9 @@ func TestValidatePodSpec(t *testing.T) {
Volumes: []api.Volume{
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: map[string]string{
"key": "value",
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: "key=value",
NodeName: "foobar",
DNSPolicy: api.DNSClusterFirst,
ActiveDeadlineSeconds: &activeDeadlineSeconds,
@@ -1156,10 +1196,8 @@ func TestValidatePod(t *testing.T) {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
NodeSelector: map[string]string{
"key": "value",
},
NodeName: "foobar",
NodeSelector: "key=value",
NodeName: "foobar",
},
},
}
@@ -126,9 +126,7 @@ func newReplicationController(replicas int) *api.ReplicationController {
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSDefault,
NodeSelector: map[string]string{
"baz": "blah",
},
NodeSelector: "baz=blah",
},
},
},
@@ -805,9 +805,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) {
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSDefault,
NodeSelector: map[string]string{
"baz": "blah",
},
NodeSelector: "baz=blah",
},
},
},
@@ -1623,7 +1623,12 @@ func (kl *Kubelet) checkNodeSelectorMatching(pods []*api.Pod) (fitting []*api.Po
return pods, nil
}
for _, pod := range pods {
if !predicates.PodMatchesNodeLabels(pod, node) {
match, err := predicates.PodMatchesNodeLabels(pod, node)
if err != nil {
glog.Errorf("error matching node selector: %v", err)
return pods, nil
}
if !match {
notFitting = append(notFitting, pod)
continue
}
@@ -2065,15 +2065,15 @@ func TestHandleNodeSelector(t *testing.T) {
Name: "podA",
Namespace: "foo",
},
Spec: api.PodSpec{NodeSelector: map[string]string{"key": "A"}},
Spec: api.PodSpec{NodeSelector: "key=A"},
},
{
ObjectMeta: api.ObjectMeta{
UID: "987654321",
Name: "podB",
Namespace: "foo",
},
Spec: api.PodSpec{NodeSelector: map[string]string{"key": "B"}},
Spec: api.PodSpec{NodeSelector: "key=B"},
},
}
// The first pod should be rejected.

0 comments on commit e8be664

Please sign in to comment.
You can’t perform that action at this time.