mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Added a NodeAffinity PreFilter that looks for node.Name MatchField terms; if exist, the pod is only evaluated against the matching nodes.
This commit is contained in:
parent
5b20b68bc9
commit
da7f085dcb
@ -20,8 +20,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config/validation"
|
||||
@ -58,6 +60,9 @@ const (
|
||||
|
||||
// errReasonEnforced is the reason for added node affinity not matching.
|
||||
errReasonEnforced = "node(s) didn't match scheduler-enforced node affinity"
|
||||
|
||||
// errReasonConflict is the reason for pod's conflicting affinity rules.
|
||||
errReasonConflict = "pod affinity terms conflict"
|
||||
)
|
||||
|
||||
// Name returns name of the plugin. It is used in logs, etc.
|
||||
@ -86,9 +91,50 @@ func (pl *NodeAffinity) EventsToRegister() []framework.ClusterEvent {
|
||||
func (pl *NodeAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
|
||||
state := &preFilterState{requiredNodeSelectorAndAffinity: nodeaffinity.GetRequiredNodeAffinity(pod)}
|
||||
cycleState.Write(preFilterStateKey, state)
|
||||
affinity := pod.Spec.Affinity
|
||||
if affinity == nil ||
|
||||
affinity.NodeAffinity == nil ||
|
||||
affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil ||
|
||||
len(affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check if there is affinity to a specific node and return it.
|
||||
terms := affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
|
||||
var nodeNames sets.String
|
||||
for _, t := range terms {
|
||||
var termNodeNames sets.String
|
||||
for _, r := range t.MatchFields {
|
||||
if r.Key == metav1.ObjectNameField && r.Operator == v1.NodeSelectorOpIn {
|
||||
// The requirements represent ANDed constraints, and so we need to
|
||||
// find the intersection of nodes.
|
||||
s := sets.NewString(r.Values...)
|
||||
if termNodeNames == nil {
|
||||
termNodeNames = s
|
||||
} else {
|
||||
termNodeNames = termNodeNames.Intersection(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if termNodeNames == nil {
|
||||
// If this term has no node.Name field affinity,
|
||||
// then all nodes are eligible because the terms are ORed.
|
||||
return nil, nil
|
||||
}
|
||||
// If the set is empty, it means the terms had affinity to different
|
||||
// sets of nodes, and since they are ANDed, then the pod will not match any node.
|
||||
if len(termNodeNames) == 0 {
|
||||
return nil, framework.NewStatus(framework.UnschedulableAndUnresolvable, errReasonConflict)
|
||||
}
|
||||
nodeNames = nodeNames.Union(termNodeNames)
|
||||
}
|
||||
if nodeNames != nil {
|
||||
return &framework.PreFilterResult{NodeNames: nodeNames}, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
// PreFilterExtensions not necessary for this plugin as state doesn't depend on pod additions or deletions.
|
||||
func (pl *NodeAffinity) PreFilterExtensions() framework.PreFilterExtensions {
|
||||
return nil
|
||||
|
@ -18,12 +18,12 @@ package nodeaffinity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
||||
@ -38,6 +38,8 @@ func TestNodeAffinity(t *testing.T) {
|
||||
labels map[string]string
|
||||
nodeName string
|
||||
wantStatus *framework.Status
|
||||
wantPreFilterStatus *framework.Status
|
||||
wantPreFilterResult *framework.PreFilterResult
|
||||
args config.NodeAffinityArgs
|
||||
disablePreFilter bool
|
||||
}{
|
||||
@ -513,7 +515,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -523,7 +525,8 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_1",
|
||||
nodeName: "node1",
|
||||
wantPreFilterResult: &framework.PreFilterResult{NodeNames: sets.NewString("node1")},
|
||||
},
|
||||
{
|
||||
name: "Pod with matchFields using In operator that does not match the existing node",
|
||||
@ -538,7 +541,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -548,8 +551,9 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
||||
wantPreFilterResult: &framework.PreFilterResult{NodeNames: sets.NewString("node1")},
|
||||
},
|
||||
{
|
||||
name: "Pod with two terms: matchFields does not match, but matchExpressions matches",
|
||||
@ -564,7 +568,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -583,7 +587,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
{
|
||||
@ -599,7 +603,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
@ -616,8 +620,9 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
wantPreFilterResult: &framework.PreFilterResult{NodeNames: sets.NewString("node1")},
|
||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
||||
},
|
||||
{
|
||||
@ -633,7 +638,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
@ -650,8 +655,9 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_1",
|
||||
nodeName: "node1",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
wantPreFilterResult: &framework.PreFilterResult{NodeNames: sets.NewString("node1")},
|
||||
},
|
||||
{
|
||||
name: "Pod with two terms: both matchFields and matchExpressions do not match",
|
||||
@ -666,7 +672,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_1"},
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -685,10 +691,78 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
||||
},
|
||||
{
|
||||
name: "Pod with two terms of node.Name affinity",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node2",
|
||||
wantPreFilterResult: &framework.PreFilterResult{NodeNames: sets.NewString("node1", "node2")},
|
||||
},
|
||||
{
|
||||
name: "Pod with two conflicting mach field requirements",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
NodeAffinity: &v1.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchFields: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node1"},
|
||||
},
|
||||
{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"foo": "bar"},
|
||||
wantPreFilterStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, errReasonConflict),
|
||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
||||
},
|
||||
{
|
||||
name: "Matches added affinity and Pod's node affinity",
|
||||
pod: &v1.Pod{
|
||||
@ -712,7 +786,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"zone": "foo"},
|
||||
args: config.NodeAffinityArgs{
|
||||
AddedAffinity: &v1.NodeAffinity{
|
||||
@ -721,7 +795,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
MatchFields: []v1.NodeSelectorRequirement{{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_2"},
|
||||
Values: []string{"node2"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
@ -751,7 +825,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"zone": "foo"},
|
||||
args: config.NodeAffinityArgs{
|
||||
AddedAffinity: &v1.NodeAffinity{
|
||||
@ -760,7 +834,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
MatchFields: []v1.NodeSelectorRequirement{{
|
||||
Key: metav1.ObjectNameField,
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"node_2"},
|
||||
Values: []string{"node2"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
@ -771,7 +845,7 @@ func TestNodeAffinity(t *testing.T) {
|
||||
{
|
||||
name: "Doesn't match added affinity",
|
||||
pod: &v1.Pod{},
|
||||
nodeName: "node_2",
|
||||
nodeName: "node2",
|
||||
labels: map[string]string{"zone": "foo"},
|
||||
args: config.NodeAffinityArgs{
|
||||
AddedAffinity: &v1.NodeAffinity{
|
||||
@ -855,14 +929,17 @@ func TestNodeAffinity(t *testing.T) {
|
||||
state := framework.NewCycleState()
|
||||
var gotStatus *framework.Status
|
||||
if !test.disablePreFilter {
|
||||
_, gotStatus = p.(framework.PreFilterPlugin).PreFilter(context.Background(), state, test.pod)
|
||||
if !gotStatus.IsSuccess() {
|
||||
t.Errorf("unexpected error: %v", gotStatus)
|
||||
gotPreFilterResult, gotStatus := p.(framework.PreFilterPlugin).PreFilter(context.Background(), state, test.pod)
|
||||
if diff := cmp.Diff(test.wantPreFilterStatus, gotStatus); diff != "" {
|
||||
t.Errorf("unexpected PreFilter Status (-want,+got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(test.wantPreFilterResult, gotPreFilterResult); diff != "" {
|
||||
t.Errorf("unexpected PreFilterResult (-want,+got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
gotStatus = p.(framework.FilterPlugin).Filter(context.Background(), state, test.pod, nodeInfo)
|
||||
if !reflect.DeepEqual(gotStatus, test.wantStatus) {
|
||||
t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus)
|
||||
if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
|
||||
t.Errorf("unexpected Filter Status (-want,+got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user