diff --git a/pkg/scheduler/algorithm/BUILD b/pkg/scheduler/algorithm/BUILD index f2230c48c36..9d2f4b7021e 100644 --- a/pkg/scheduler/algorithm/BUILD +++ b/pkg/scheduler/algorithm/BUILD @@ -16,6 +16,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/scheduler/algorithm", deps = [ + "//pkg/apis/core:go_default_library", "//pkg/scheduler/api:go_default_library", "//pkg/scheduler/schedulercache:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", diff --git a/pkg/scheduler/algorithm/predicates/BUILD b/pkg/scheduler/algorithm/predicates/BUILD index d85c822872c..069dac60931 100644 --- a/pkg/scheduler/algorithm/predicates/BUILD +++ b/pkg/scheduler/algorithm/predicates/BUILD @@ -32,6 +32,7 @@ go_library( "//vendor/k8s.io/api/storage/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/scheduler/algorithm/predicates/predicates.go b/pkg/scheduler/algorithm/predicates/predicates.go index abca33adbee..879f827ae65 100644 --- a/pkg/scheduler/algorithm/predicates/predicates.go +++ b/pkg/scheduler/algorithm/predicates/predicates.go @@ -24,10 +24,12 @@ import ( "sync" "github.com/golang/glog" + "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/sets" @@ -775,21 +777,16 @@ func PodFitsResources(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInfo *s // nodeMatchesNodeSelectorTerms checks if a node's labels satisfy a list of node selector terms, // terms are ORed, and an empty list of terms will match nothing. func nodeMatchesNodeSelectorTerms(node *v1.Node, nodeSelectorTerms []v1.NodeSelectorTerm) bool { - for _, req := range nodeSelectorTerms { - nodeSelector, err := v1helper.NodeSelectorRequirementsAsSelector(req.MatchExpressions) - if err != nil { - glog.V(10).Infof("Failed to parse MatchExpressions: %+v, regarding as not match.", req.MatchExpressions) - return false - } - if nodeSelector.Matches(labels.Set(node.Labels)) { - return true - } + nodeFields := map[string]string{} + for k, f := range algorithm.NodeFieldSelectorKeys { + nodeFields[k] = f(node) } - return false + return v1helper.MatchNodeSelectorTerms(nodeSelectorTerms, labels.Set(node.Labels), fields.Set(nodeFields)) } -// The pod can only schedule onto nodes that satisfy requirements in both NodeAffinity and nodeSelector. -func podMatchesNodeLabels(pod *v1.Pod, node *v1.Node) bool { +// podMatchesNodeSelectorAndAffinityTerms checks whether the pod is schedulable onto nodes according to +// the requirements in both NodeAffinity and nodeSelector. +func podMatchesNodeSelectorAndAffinityTerms(pod *v1.Pod, node *v1.Node) bool { // Check if node.Labels match pod.Spec.NodeSelector. if len(pod.Spec.NodeSelector) > 0 { selector := labels.SelectorFromSet(pod.Spec.NodeSelector) @@ -840,7 +837,7 @@ func PodMatchNodeSelector(pod *v1.Pod, meta algorithm.PredicateMetadata, nodeInf if node == nil { return false, nil, fmt.Errorf("node not found") } - if podMatchesNodeLabels(pod, node) { + if podMatchesNodeSelectorAndAffinityTerms(pod, node) { return true, nil, nil } return false, []algorithm.PredicateFailureReason{ErrNodeSelectorNotMatch}, nil diff --git a/pkg/scheduler/algorithm/predicates/predicates_test.go b/pkg/scheduler/algorithm/predicates/predicates_test.go index b34ccdb0d36..ab757640b0a 100644 --- a/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -911,10 +911,11 @@ func TestISCSIDiskConflicts(t *testing.T) { // TODO: Add test case for RequiredDuringSchedulingRequiredDuringExecution after it's implemented. func TestPodFitsSelector(t *testing.T) { tests := []struct { - pod *v1.Pod - labels map[string]string - fits bool - test string + pod *v1.Pod + labels map[string]string + nodeName string + fits bool + test string }{ { pod: &v1.Pod{}, @@ -1387,11 +1388,206 @@ func TestPodFitsSelector(t *testing.T) { fits: false, test: "Pod with an invalid value in Affinity term won't be scheduled onto the node", }, + { + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: algorithm.NodeFieldSelectorKeyNodeName, + Operator: v1.NodeSelectorOpIn, + Values: []string{"node_1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + nodeName: "node_1", + fits: true, + test: "Pod with matchFields using In operator that matches the existing node", + }, + { + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: algorithm.NodeFieldSelectorKeyNodeName, + Operator: v1.NodeSelectorOpIn, + Values: []string{"node_1"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + nodeName: "node_2", + fits: false, + test: "Pod with matchFields using In operator that does not match the existing node", + }, + { + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: algorithm.NodeFieldSelectorKeyNodeName, + Operator: v1.NodeSelectorOpIn, + Values: []string{"node_1"}, + }, + }, + }, + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "foo", + Operator: v1.NodeSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + nodeName: "node_2", + labels: map[string]string{"foo": "bar"}, + fits: true, + test: "Pod with two terms: matchFields does not match, but matchExpressions matches", + }, + { + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: algorithm.NodeFieldSelectorKeyNodeName, + Operator: v1.NodeSelectorOpIn, + Values: []string{"node_1"}, + }, + }, + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "foo", + Operator: v1.NodeSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + nodeName: "node_2", + labels: map[string]string{"foo": "bar"}, + fits: false, + test: "Pod with one term: matchFields does not match, but matchExpressions matches", + }, + { + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: algorithm.NodeFieldSelectorKeyNodeName, + Operator: v1.NodeSelectorOpIn, + Values: []string{"node_1"}, + }, + }, + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "foo", + Operator: v1.NodeSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + nodeName: "node_1", + labels: map[string]string{"foo": "bar"}, + fits: true, + test: "Pod with one term: both matchFields and matchExpressions match", + }, + { + pod: &v1.Pod{ + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: algorithm.NodeFieldSelectorKeyNodeName, + Operator: v1.NodeSelectorOpIn, + Values: []string{"node_1"}, + }, + }, + }, + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "foo", + Operator: v1.NodeSelectorOpIn, + Values: []string{"not-match-to-bar"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + nodeName: "node_2", + labels: map[string]string{"foo": "bar"}, + fits: false, + test: "Pod with two terms: both matchFields and matchExpressions do not match", + }, } expectedFailureReasons := []algorithm.PredicateFailureReason{ErrNodeSelectorNotMatch} for _, test := range tests { - node := v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: test.labels}} + node := v1.Node{ObjectMeta: metav1.ObjectMeta{ + Name: test.nodeName, + Labels: test.labels, + }} nodeInfo := schedulercache.NewNodeInfo() nodeInfo.SetNode(&node) diff --git a/pkg/scheduler/algorithm/types.go b/pkg/scheduler/algorithm/types.go index 1ebf50d5c9b..e0109d089a8 100644 --- a/pkg/scheduler/algorithm/types.go +++ b/pkg/scheduler/algorithm/types.go @@ -25,6 +25,12 @@ import ( "k8s.io/kubernetes/pkg/scheduler/schedulercache" ) +// NodeFieldSelectorKeys is a map that: the key are node field selector keys; the values are +// the functions to get the value of the node field. +var NodeFieldSelectorKeys = map[string]func(*v1.Node) string{ + NodeFieldSelectorKeyNodeName: func(n *v1.Node) string { return n.Name }, +} + // FitPredicate is a function that indicates if a pod fits into an existing node. // The failure information is given by the error. type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []PredicateFailureReason, error) diff --git a/pkg/scheduler/algorithm/well_known_labels.go b/pkg/scheduler/algorithm/well_known_labels.go index 887b5b862fc..3482649b416 100644 --- a/pkg/scheduler/algorithm/well_known_labels.go +++ b/pkg/scheduler/algorithm/well_known_labels.go @@ -16,6 +16,10 @@ limitations under the License. package algorithm +import ( + api "k8s.io/kubernetes/pkg/apis/core" +) + const ( // TaintNodeNotReady will be added when node is not ready // and feature-gate for TaintBasedEvictions flag is enabled, @@ -74,4 +78,8 @@ const ( // TaintNodeShutdown when node is shutdown in external cloud provider TaintNodeShutdown = "node.cloudprovider.kubernetes.io/shutdown" + + // NodeFieldSelectorKeyNodeName ('metadata.name') uses this as node field selector key + // when selecting node by node's name. + NodeFieldSelectorKeyNodeName = api.ObjectNameField )