mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +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"
|
"context"
|
||||||
"fmt"
|
"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/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/apis/config/validation"
|
"k8s.io/kubernetes/pkg/scheduler/apis/config/validation"
|
||||||
@ -58,6 +60,9 @@ const (
|
|||||||
|
|
||||||
// errReasonEnforced is the reason for added node affinity not matching.
|
// errReasonEnforced is the reason for added node affinity not matching.
|
||||||
errReasonEnforced = "node(s) didn't match scheduler-enforced node affinity"
|
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.
|
// Name returns name of the plugin. It is used in logs, etc.
|
||||||
@ -86,7 +91,48 @@ func (pl *NodeAffinity) EventsToRegister() []framework.ClusterEvent {
|
|||||||
func (pl *NodeAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
|
func (pl *NodeAffinity) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) (*framework.PreFilterResult, *framework.Status) {
|
||||||
state := &preFilterState{requiredNodeSelectorAndAffinity: nodeaffinity.GetRequiredNodeAffinity(pod)}
|
state := &preFilterState{requiredNodeSelectorAndAffinity: nodeaffinity.GetRequiredNodeAffinity(pod)}
|
||||||
cycleState.Write(preFilterStateKey, state)
|
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
|
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.
|
// PreFilterExtensions not necessary for this plugin as state doesn't depend on pod additions or deletions.
|
||||||
|
@ -18,12 +18,12 @@ package nodeaffinity
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/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/apis/config"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework"
|
"k8s.io/kubernetes/pkg/scheduler/framework"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
|
||||||
@ -38,6 +38,8 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
labels map[string]string
|
labels map[string]string
|
||||||
nodeName string
|
nodeName string
|
||||||
wantStatus *framework.Status
|
wantStatus *framework.Status
|
||||||
|
wantPreFilterStatus *framework.Status
|
||||||
|
wantPreFilterResult *framework.PreFilterResult
|
||||||
args config.NodeAffinityArgs
|
args config.NodeAffinityArgs
|
||||||
disablePreFilter bool
|
disablePreFilter bool
|
||||||
}{
|
}{
|
||||||
@ -513,7 +515,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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",
|
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,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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),
|
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",
|
name: "Pod with two terms: matchFields does not match, but matchExpressions matches",
|
||||||
@ -564,7 +568,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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"},
|
labels: map[string]string{"foo": "bar"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -599,7 +603,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
Operator: v1.NodeSelectorOpIn,
|
||||||
Values: []string{"node_1"},
|
Values: []string{"node1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||||
@ -616,8 +620,9 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nodeName: "node_2",
|
nodeName: "node2",
|
||||||
labels: map[string]string{"foo": "bar"},
|
labels: map[string]string{"foo": "bar"},
|
||||||
|
wantPreFilterResult: &framework.PreFilterResult{NodeNames: sets.NewString("node1")},
|
||||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -633,7 +638,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
Operator: v1.NodeSelectorOpIn,
|
||||||
Values: []string{"node_1"},
|
Values: []string{"node1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||||
@ -650,8 +655,9 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nodeName: "node_1",
|
nodeName: "node1",
|
||||||
labels: map[string]string{"foo": "bar"},
|
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",
|
name: "Pod with two terms: both matchFields and matchExpressions do not match",
|
||||||
@ -666,7 +672,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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"},
|
labels: map[string]string{"foo": "bar"},
|
||||||
wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonPod),
|
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",
|
name: "Matches added affinity and Pod's node affinity",
|
||||||
pod: &v1.Pod{
|
pod: &v1.Pod{
|
||||||
@ -712,7 +786,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nodeName: "node_2",
|
nodeName: "node2",
|
||||||
labels: map[string]string{"zone": "foo"},
|
labels: map[string]string{"zone": "foo"},
|
||||||
args: config.NodeAffinityArgs{
|
args: config.NodeAffinityArgs{
|
||||||
AddedAffinity: &v1.NodeAffinity{
|
AddedAffinity: &v1.NodeAffinity{
|
||||||
@ -721,7 +795,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
MatchFields: []v1.NodeSelectorRequirement{{
|
MatchFields: []v1.NodeSelectorRequirement{{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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"},
|
labels: map[string]string{"zone": "foo"},
|
||||||
args: config.NodeAffinityArgs{
|
args: config.NodeAffinityArgs{
|
||||||
AddedAffinity: &v1.NodeAffinity{
|
AddedAffinity: &v1.NodeAffinity{
|
||||||
@ -760,7 +834,7 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
MatchFields: []v1.NodeSelectorRequirement{{
|
MatchFields: []v1.NodeSelectorRequirement{{
|
||||||
Key: metav1.ObjectNameField,
|
Key: metav1.ObjectNameField,
|
||||||
Operator: v1.NodeSelectorOpIn,
|
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",
|
name: "Doesn't match added affinity",
|
||||||
pod: &v1.Pod{},
|
pod: &v1.Pod{},
|
||||||
nodeName: "node_2",
|
nodeName: "node2",
|
||||||
labels: map[string]string{"zone": "foo"},
|
labels: map[string]string{"zone": "foo"},
|
||||||
args: config.NodeAffinityArgs{
|
args: config.NodeAffinityArgs{
|
||||||
AddedAffinity: &v1.NodeAffinity{
|
AddedAffinity: &v1.NodeAffinity{
|
||||||
@ -855,14 +929,17 @@ func TestNodeAffinity(t *testing.T) {
|
|||||||
state := framework.NewCycleState()
|
state := framework.NewCycleState()
|
||||||
var gotStatus *framework.Status
|
var gotStatus *framework.Status
|
||||||
if !test.disablePreFilter {
|
if !test.disablePreFilter {
|
||||||
_, gotStatus = p.(framework.PreFilterPlugin).PreFilter(context.Background(), state, test.pod)
|
gotPreFilterResult, gotStatus := p.(framework.PreFilterPlugin).PreFilter(context.Background(), state, test.pod)
|
||||||
if !gotStatus.IsSuccess() {
|
if diff := cmp.Diff(test.wantPreFilterStatus, gotStatus); diff != "" {
|
||||||
t.Errorf("unexpected error: %v", gotStatus)
|
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)
|
gotStatus = p.(framework.FilterPlugin).Filter(context.Background(), state, test.pod, nodeInfo)
|
||||||
if !reflect.DeepEqual(gotStatus, test.wantStatus) {
|
if diff := cmp.Diff(test.wantStatus, gotStatus); diff != "" {
|
||||||
t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus)
|
t.Errorf("unexpected Filter Status (-want,+got):\n%s", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user