2019-10-09 02:19:24 -04:00
/ *
Copyright 2019 The Kubernetes Authors .
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 nodeaffinity
import (
2019-08-28 07:12:02 -04:00
"context"
2023-03-30 20:04:01 -04:00
"fmt"
2019-10-09 02:19:24 -04:00
"testing"
2020-10-23 13:21:10 -04:00
"github.com/google/go-cmp/cmp"
2023-12-13 22:08:50 -05:00
"github.com/stretchr/testify/require"
2019-08-28 07:12:02 -04:00
v1 "k8s.io/api/core/v1"
2019-10-09 02:19:24 -04:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022-03-14 15:37:03 -04:00
"k8s.io/apimachinery/pkg/util/sets"
2023-05-15 06:36:17 -04:00
"k8s.io/klog/v2/ktesting"
2025-06-26 11:06:29 -04:00
fwk "k8s.io/kube-scheduler/framework"
2020-10-23 13:21:10 -04:00
"k8s.io/kubernetes/pkg/scheduler/apis/config"
2024-08-25 00:10:29 -04:00
"k8s.io/kubernetes/pkg/scheduler/backend/cache"
2020-10-09 10:41:44 -04:00
"k8s.io/kubernetes/pkg/scheduler/framework"
2024-09-08 01:54:46 -04:00
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
2020-06-19 05:05:45 -04:00
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
2022-05-05 13:48:55 -04:00
st "k8s.io/kubernetes/pkg/scheduler/testing"
2023-11-18 05:21:58 -05:00
tf "k8s.io/kubernetes/pkg/scheduler/testing/framework"
2019-10-09 02:19:24 -04:00
)
// TODO: Add test case for RequiredDuringSchedulingRequiredDuringExecution after it's implemented.
func TestNodeAffinity ( t * testing . T ) {
tests := [ ] struct {
2022-03-14 15:37:03 -04:00
name string
pod * v1 . Pod
labels map [ string ] string
nodeName string
2025-06-03 18:59:50 -04:00
wantStatus * fwk . Status
wantPreFilterStatus * fwk . Status
2025-07-24 07:48:07 -04:00
wantPreFilterResult * fwk . PreFilterResult
2022-03-14 15:37:03 -04:00
args config . NodeAffinityArgs
2023-03-12 03:06:26 -04:00
runPreFilter bool
2019-10-09 02:19:24 -04:00
} {
{
2021-02-18 15:58:05 -05:00
name : "missing labels" ,
2022-05-05 13:48:55 -04:00
pod : st . MakePod ( ) . NodeSelector ( map [ string ] string {
"foo" : "bar" ,
} ) . Obj ( ) ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "same labels" ,
2022-05-05 13:48:55 -04:00
pod : st . MakePod ( ) . NodeSelector ( map [ string ] string {
"foo" : "bar" ,
} ) . Obj ( ) ,
2019-10-09 02:19:24 -04:00
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "node labels are superset" ,
2022-05-05 13:48:55 -04:00
pod : st . MakePod ( ) . NodeSelector ( map [ string ] string {
"foo" : "bar" ,
} ) . Obj ( ) ,
2019-10-09 02:19:24 -04:00
labels : map [ string ] string {
"foo" : "bar" ,
"baz" : "blah" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "node labels are subset" ,
2022-05-05 13:48:55 -04:00
pod : st . MakePod ( ) . NodeSelector ( map [ string ] string {
"foo" : "bar" ,
"baz" : "blah" ,
} ) . Obj ( ) ,
2019-10-09 02:19:24 -04:00
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with matchExpressions using In operator that matches the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" , "value2" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with matchExpressions using Gt operator that matches the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "kernel-version" ,
Operator : v1 . NodeSelectorOpGt ,
Values : [ ] string { "0204" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
// We use two digit to denote major version and two digit for minor version.
"kernel-version" : "0206" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with matchExpressions using NotIn operator that matches the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "mem-type" ,
Operator : v1 . NodeSelectorOpNotIn ,
Values : [ ] string { "DDR" , "DDR2" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"mem-type" : "DDR3" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with matchExpressions using Exists operator that matches the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "GPU" ,
Operator : v1 . NodeSelectorOpExists ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"GPU" : "NVIDIA-GRID-K1" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with affinity that don't match node's labels won't schedule onto the node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "value1" , "value2" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with empty MatchExpressions is not a valid value will match no objects and won't schedule onto the node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement { } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with no Affinity will schedule onto a node" ,
pod : & v1 . Pod { } ,
2019-10-09 02:19:24 -04:00
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2025-06-03 18:59:50 -04:00
wantPreFilterStatus : fwk . NewStatus ( fwk . Skip ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with Affinity but nil NodeSelector will schedule onto a node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : nil ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2025-06-03 18:59:50 -04:00
wantPreFilterStatus : fwk . NewStatus ( fwk . Skip ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with multiple matchExpressions ANDed that matches the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "GPU" ,
Operator : v1 . NodeSelectorOpExists ,
} , {
Key : "GPU" ,
Operator : v1 . NodeSelectorOpNotIn ,
Values : [ ] string { "AMD" , "INTER" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"GPU" : "NVIDIA-GRID-K1" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with multiple matchExpressions ANDed that doesn't match the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "GPU" ,
Operator : v1 . NodeSelectorOpExists ,
} , {
Key : "GPU" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "AMD" , "INTER" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"GPU" : "NVIDIA-GRID-K1" ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with multiple NodeSelectorTerms ORed in affinity, matches the node's labels and will schedule onto the node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" , "value2" } ,
} ,
} ,
} ,
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "diffkey" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "wrong" , "value2" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with an Affinity and a PodSpec.NodeSelector(the old thing that we are deprecating) " +
"both are satisfied, will schedule onto the node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
NodeSelector : map [ string ] string {
"foo" : "bar" ,
} ,
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpExists ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with an Affinity matches node's labels but the PodSpec.NodeSelector(the old thing that we are deprecating) " +
"is not satisfied, won't schedule onto the node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
NodeSelector : map [ string ] string {
"foo" : "bar" ,
} ,
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpExists ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "barrrrrr" ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with an invalid value in Affinity term won't be scheduled onto the node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpNotIn ,
Values : [ ] string { "invalid value: ___@#$%^" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with matchFields using In operator that matches the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchFields : [ ] v1 . NodeSelectorRequirement {
{
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2019-10-09 02:19:24 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node1" } ,
2019-10-09 02:19:24 -04:00
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2022-03-14 15:37:03 -04:00
nodeName : "node1" ,
2025-07-24 07:48:07 -04:00
wantPreFilterResult : & fwk . PreFilterResult { NodeNames : sets . New ( "node1" ) } ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with matchFields using In operator that does not match the existing node" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchFields : [ ] v1 . NodeSelectorRequirement {
{
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2019-10-09 02:19:24 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node1" } ,
2019-10-09 02:19:24 -04:00
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2022-03-14 15:37:03 -04:00
nodeName : "node2" ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2025-07-24 07:48:07 -04:00
wantPreFilterResult : & fwk . PreFilterResult { NodeNames : sets . New ( "node1" ) } ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with two terms: matchFields does not match, but matchExpressions matches" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchFields : [ ] v1 . NodeSelectorRequirement {
{
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2019-10-09 02:19:24 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node1" } ,
2019-10-09 02:19:24 -04:00
} ,
2023-03-10 07:08:31 -05:00
{
Key : metav1 . ObjectNameField ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "node2" } ,
} ,
2019-10-09 02:19:24 -04:00
} ,
} ,
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2023-03-12 03:06:26 -04:00
nodeName : "node2" ,
labels : map [ string ] string { "foo" : "bar" } ,
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with one term: matchFields does not match, but matchExpressions matches" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchFields : [ ] v1 . NodeSelectorRequirement {
{
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2019-10-09 02:19:24 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node1" } ,
2019-10-09 02:19:24 -04:00
} ,
} ,
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2022-03-14 15:37:03 -04:00
nodeName : "node2" ,
labels : map [ string ] string { "foo" : "bar" } ,
2025-07-24 07:48:07 -04:00
wantPreFilterResult : & fwk . PreFilterResult { NodeNames : sets . New ( "node1" ) } ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with one term: both matchFields and matchExpressions match" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchFields : [ ] v1 . NodeSelectorRequirement {
{
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2019-10-09 02:19:24 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node1" } ,
2019-10-09 02:19:24 -04:00
} ,
} ,
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2022-03-14 15:37:03 -04:00
nodeName : "node1" ,
labels : map [ string ] string { "foo" : "bar" } ,
2025-07-24 07:48:07 -04:00
wantPreFilterResult : & fwk . PreFilterResult { NodeNames : sets . New ( "node1" ) } ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Pod with two terms: both matchFields and matchExpressions do not match" ,
2019-10-09 02:19:24 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchFields : [ ] v1 . NodeSelectorRequirement {
{
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2019-10-09 02:19:24 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node1" } ,
2019-10-09 02:19:24 -04:00
} ,
} ,
} ,
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "not-match-to-bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2023-03-12 03:06:26 -04:00
nodeName : "node2" ,
labels : map [ string ] string { "foo" : "bar" } ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2020-10-23 13:21:10 -04:00
} ,
2022-03-14 15:37:03 -04:00
{
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" ,
2025-07-24 07:48:07 -04:00
wantPreFilterResult : & fwk . PreFilterResult { NodeNames : sets . New ( "node1" , "node2" ) } ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2022-03-14 15:37:03 -04:00
} ,
{
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" } ,
2025-06-03 18:59:50 -04:00
wantPreFilterStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , errReasonConflict ) ,
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2022-03-14 15:37:03 -04:00
} ,
2020-10-23 13:21:10 -04:00
{
2021-02-18 15:58:05 -05:00
name : "Matches added affinity and Pod's node affinity" ,
2020-10-23 13:21:10 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "zone" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "foo" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2022-03-14 15:37:03 -04:00
nodeName : "node2" ,
2020-10-23 13:21:10 -04:00
labels : map [ string ] string { "zone" : "foo" } ,
args : config . NodeAffinityArgs {
AddedAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm { {
MatchFields : [ ] v1 . NodeSelectorRequirement { {
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2020-10-23 13:21:10 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node2" } ,
2020-10-23 13:21:10 -04:00
} } ,
} } ,
} ,
} ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2020-10-23 13:21:10 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Matches added affinity but not Pod's node affinity" ,
2020-10-23 13:21:10 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "zone" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2022-03-14 15:37:03 -04:00
nodeName : "node2" ,
2020-10-23 13:21:10 -04:00
labels : map [ string ] string { "zone" : "foo" } ,
args : config . NodeAffinityArgs {
AddedAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm { {
MatchFields : [ ] v1 . NodeSelectorRequirement { {
2021-02-24 05:06:29 -05:00
Key : metav1 . ObjectNameField ,
2020-10-23 13:21:10 -04:00
Operator : v1 . NodeSelectorOpIn ,
2022-03-14 15:37:03 -04:00
Values : [ ] string { "node2" } ,
2020-10-23 13:21:10 -04:00
} } ,
} } ,
} ,
} ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , ErrReasonPod ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2020-10-23 13:21:10 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "Doesn't match added affinity" ,
2020-10-23 13:21:10 -04:00
pod : & v1 . Pod { } ,
2022-03-14 15:37:03 -04:00
nodeName : "node2" ,
2020-10-23 13:21:10 -04:00
labels : map [ string ] string { "zone" : "foo" } ,
args : config . NodeAffinityArgs {
AddedAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm { {
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "zone" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} } ,
} ,
} ,
} ,
2025-06-03 18:59:50 -04:00
wantStatus : fwk . NewStatus ( fwk . UnschedulableAndUnresolvable , errReasonEnforced ) ,
2023-03-12 03:06:26 -04:00
runPreFilter : true ,
2019-10-09 02:19:24 -04:00
} ,
2021-02-18 15:58:05 -05:00
{
name : "Matches node selector correctly even if PreFilter is not called" ,
pod : & v1 . Pod {
Spec : v1 . PodSpec {
NodeSelector : map [ string ] string {
"foo" : "bar" ,
} ,
} ,
} ,
labels : map [ string ] string {
"foo" : "bar" ,
"baz" : "blah" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : false ,
2021-02-18 15:58:05 -05:00
} ,
{
name : "Matches node affinity correctly even if PreFilter is not called" ,
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "GPU" ,
Operator : v1 . NodeSelectorOpExists ,
} , {
Key : "GPU" ,
Operator : v1 . NodeSelectorOpNotIn ,
Values : [ ] string { "AMD" , "INTER" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
labels : map [ string ] string {
"GPU" : "NVIDIA-GRID-K1" ,
} ,
2023-03-12 03:06:26 -04:00
runPreFilter : false ,
2021-02-18 15:58:05 -05:00
} ,
2019-10-09 02:19:24 -04:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-09-05 23:55:33 -04:00
_ , ctx := ktesting . NewTestContext ( t )
2019-10-09 02:19:24 -04:00
node := v1 . Node { ObjectMeta : metav1 . ObjectMeta {
Name : test . nodeName ,
Labels : test . labels ,
} }
2020-04-06 21:40:15 -04:00
nodeInfo := framework . NewNodeInfo ( )
2019-10-09 02:19:24 -04:00
nodeInfo . SetNode ( & node )
2024-09-08 01:54:46 -04:00
p , err := New ( ctx , & test . args , nil , feature . Features { } )
2020-10-23 13:21:10 -04:00
if err != nil {
t . Fatalf ( "Creating plugin: %v" , err )
}
2021-02-18 15:58:05 -05:00
state := framework . NewCycleState ( )
2025-06-03 18:59:50 -04:00
var gotStatus * fwk . Status
2023-03-12 03:06:26 -04:00
if test . runPreFilter {
2025-07-24 07:48:07 -04:00
gotPreFilterResult , gotStatus := p . ( fwk . PreFilterPlugin ) . PreFilter ( ctx , state , test . pod , nil )
2022-03-14 15:37:03 -04:00
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 )
2021-02-18 15:58:05 -05:00
}
}
2025-07-24 07:48:07 -04:00
gotStatus = p . ( fwk . FilterPlugin ) . Filter ( ctx , state , test . pod , nodeInfo )
2022-03-14 15:37:03 -04:00
if diff := cmp . Diff ( test . wantStatus , gotStatus ) ; diff != "" {
t . Errorf ( "unexpected Filter Status (-want,+got):\n%s" , diff )
2019-10-09 02:19:24 -04:00
}
} )
}
}
2019-10-16 04:42:26 -04:00
func TestNodeAffinityPriority ( t * testing . T ) {
label1 := map [ string ] string { "foo" : "bar" }
label2 := map [ string ] string { "key" : "value" }
label3 := map [ string ] string { "az" : "az1" }
label4 := map [ string ] string { "abc" : "az11" , "def" : "az22" }
label5 := map [ string ] string { "foo" : "bar" , "key" : "value" , "az" : "az1" }
affinity1 := & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
PreferredDuringSchedulingIgnoredDuringExecution : [ ] v1 . PreferredSchedulingTerm { {
Weight : 2 ,
Preference : v1 . NodeSelectorTerm {
MatchExpressions : [ ] v1 . NodeSelectorRequirement { {
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} } ,
} ,
} } ,
} ,
}
affinity2 := & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
PreferredDuringSchedulingIgnoredDuringExecution : [ ] v1 . PreferredSchedulingTerm {
{
Weight : 2 ,
Preference : v1 . NodeSelectorTerm {
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
{
Weight : 4 ,
Preference : v1 . NodeSelectorTerm {
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "key" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "value" } ,
} ,
} ,
} ,
} ,
{
Weight : 5 ,
Preference : v1 . NodeSelectorTerm {
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
{
Key : "key" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "value" } ,
} ,
{
Key : "az" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "az1" } ,
} ,
} ,
} ,
} ,
} ,
} ,
}
tests := [ ] struct {
2023-03-30 20:04:01 -04:00
name string
pod * v1 . Pod
nodes [ ] * v1 . Node
2025-07-24 07:48:07 -04:00
expectedList fwk . NodeScoreList
2023-03-30 20:04:01 -04:00
args config . NodeAffinityArgs
runPreScore bool
2025-06-03 18:59:50 -04:00
wantPreScoreStatus * fwk . Status
2019-10-16 04:42:26 -04:00
} {
{
2022-05-05 13:48:55 -04:00
name : "all nodes are same priority as NodeAffinity is nil" ,
2019-10-16 04:42:26 -04:00
pod : & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Annotations : map [ string ] string { } ,
} ,
} ,
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node3" , Labels : label3 } } ,
2019-10-16 04:42:26 -04:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : 0 } , { Name : "node2" , Score : 0 } , { Name : "node3" , Score : 0 } } ,
2023-03-30 20:04:01 -04:00
} ,
{
// PreScore returns Skip.
name : "Skip is returned in PreScore when NodeAffinity is nil" ,
pod : & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Annotations : map [ string ] string { } ,
} ,
} ,
nodes : [ ] * v1 . Node {
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
} ,
runPreScore : true ,
2025-06-03 18:59:50 -04:00
wantPreScoreStatus : fwk . NewStatus ( fwk . Skip ) ,
2023-03-30 20:04:01 -04:00
} ,
{
name : "PreScore returns error when an incoming Pod has a broken affinity" ,
pod : & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Annotations : map [ string ] string { } ,
} ,
Spec : v1 . PodSpec {
Affinity : & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
PreferredDuringSchedulingIgnoredDuringExecution : [ ] v1 . PreferredSchedulingTerm {
{
Weight : 2 ,
Preference : v1 . NodeSelectorTerm {
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "invalid key" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
nodes : [ ] * v1 . Node {
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
} ,
runPreScore : true ,
2025-06-03 18:59:50 -04:00
wantPreScoreStatus : fwk . AsStatus ( fmt . Errorf ( ` [0].matchExpressions[0].key: Invalid value: "invalid key": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]') ` ) ) ,
2019-10-16 04:42:26 -04:00
} ,
{
2022-05-05 13:48:55 -04:00
name : "no node matches preferred scheduling requirements in NodeAffinity of pod so all nodes' priority is zero" ,
2019-10-16 04:42:26 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : affinity1 ,
} ,
} ,
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label4 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node3" , Labels : label3 } } ,
2019-10-16 04:42:26 -04:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : 0 } , { Name : "node2" , Score : 0 } , { Name : "node3" , Score : 0 } } ,
2023-03-12 03:06:26 -04:00
runPreScore : true ,
2019-10-16 04:42:26 -04:00
} ,
{
2022-05-05 13:48:55 -04:00
name : "only node1 matches the preferred scheduling requirements of pod" ,
2019-10-16 04:42:26 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : affinity1 ,
} ,
} ,
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node3" , Labels : label3 } } ,
2019-10-16 04:42:26 -04:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : fwk . MaxNodeScore } , { Name : "node2" , Score : 0 } , { Name : "node3" , Score : 0 } } ,
2023-03-12 03:06:26 -04:00
runPreScore : true ,
2019-10-16 04:42:26 -04:00
} ,
{
2022-05-05 13:48:55 -04:00
name : "all nodes matches the preferred scheduling requirements of pod but with different priorities " ,
2019-10-16 04:42:26 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : affinity2 ,
} ,
} ,
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node5" , Labels : label5 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
2019-10-16 04:42:26 -04:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : 18 } , { Name : "node5" , Score : fwk . MaxNodeScore } , { Name : "node2" , Score : 36 } } ,
2023-03-12 03:06:26 -04:00
runPreScore : true ,
2019-10-16 04:42:26 -04:00
} ,
2020-10-23 13:21:10 -04:00
{
2021-02-18 15:58:05 -05:00
name : "added affinity" ,
pod : & v1 . Pod { } ,
2020-10-23 13:21:10 -04:00
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
2020-10-23 13:21:10 -04:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : fwk . MaxNodeScore } , { Name : "node2" , Score : 0 } } ,
2020-10-23 13:21:10 -04:00
args : config . NodeAffinityArgs {
AddedAffinity : affinity1 . NodeAffinity ,
} ,
2023-03-12 03:06:26 -04:00
runPreScore : true ,
2020-10-23 13:21:10 -04:00
} ,
{
2021-02-18 15:58:05 -05:00
name : "added affinity and pod has default affinity" ,
2020-10-23 13:21:10 -04:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : affinity1 ,
} ,
} ,
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node3" , Labels : label5 } } ,
2020-10-23 13:21:10 -04:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : 40 } , { Name : "node2" , Score : 60 } , { Name : "node3" , Score : fwk . MaxNodeScore } } ,
2020-10-23 13:21:10 -04:00
args : config . NodeAffinityArgs {
AddedAffinity : & v1 . NodeAffinity {
PreferredDuringSchedulingIgnoredDuringExecution : [ ] v1 . PreferredSchedulingTerm {
{
Weight : 3 ,
Preference : v1 . NodeSelectorTerm {
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "key" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "value" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
2023-03-12 03:06:26 -04:00
runPreScore : true ,
2020-10-23 13:21:10 -04:00
} ,
2020-11-25 22:19:52 -05:00
{
2021-02-18 15:58:05 -05:00
name : "calculate the priorities correctly even if PreScore is not called" ,
2020-11-25 22:19:52 -05:00
pod : & v1 . Pod {
Spec : v1 . PodSpec {
Affinity : affinity2 ,
} ,
} ,
nodes : [ ] * v1 . Node {
2022-05-05 13:48:55 -04:00
{ ObjectMeta : metav1 . ObjectMeta { Name : "node1" , Labels : label1 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node5" , Labels : label5 } } ,
{ ObjectMeta : metav1 . ObjectMeta { Name : "node2" , Labels : label2 } } ,
2020-11-25 22:19:52 -05:00
} ,
2025-07-24 07:48:07 -04:00
expectedList : [ ] fwk . NodeScore { { Name : "node1" , Score : 18 } , { Name : "node5" , Score : fwk . MaxNodeScore } , { Name : "node2" , Score : 36 } } ,
2020-11-25 22:19:52 -05:00
} ,
2019-10-16 04:42:26 -04:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2023-05-15 06:36:17 -04:00
_ , ctx := ktesting . NewTestContext ( t )
ctx , cancel := context . WithCancel ( ctx )
2022-08-05 12:05:22 -04:00
defer cancel ( )
2019-10-16 04:42:26 -04:00
state := framework . NewCycleState ( )
2023-05-15 06:36:17 -04:00
fh , _ := runtime . NewFramework ( ctx , nil , nil , runtime . WithSnapshotSharedLister ( cache . NewSnapshot ( nil , test . nodes ) ) )
2024-09-08 01:54:46 -04:00
p , err := New ( ctx , & test . args , fh , feature . Features { } )
2020-10-23 13:21:10 -04:00
if err != nil {
t . Fatalf ( "Creating plugin: %v" , err )
}
2025-06-03 18:59:50 -04:00
var status * fwk . Status
2023-03-12 03:06:26 -04:00
if test . runPreScore {
2025-07-24 07:48:07 -04:00
status = p . ( fwk . PreScorePlugin ) . PreScore ( ctx , state , test . pod , tf . BuildNodeInfos ( test . nodes ) )
2023-03-30 20:04:01 -04:00
if status . Code ( ) != test . wantPreScoreStatus . Code ( ) {
t . Errorf ( "unexpected status code from PreScore: want: %v got: %v" , test . wantPreScoreStatus . Code ( ) . String ( ) , status . Code ( ) . String ( ) )
}
if status . Message ( ) != test . wantPreScoreStatus . Message ( ) {
t . Errorf ( "unexpected status message from PreScore: want: %v got: %v" , test . wantPreScoreStatus . Message ( ) , status . Message ( ) )
}
2020-11-25 22:19:52 -05:00
if ! status . IsSuccess ( ) {
2023-03-30 20:04:01 -04:00
// no need to proceed.
return
2020-11-25 22:19:52 -05:00
}
}
2025-07-24 07:48:07 -04:00
var gotList fwk . NodeScoreList
2025-03-03 05:24:16 -05:00
nodeInfos := tf . BuildNodeInfos ( test . nodes )
for _ , nodeInfo := range nodeInfos {
nodeName := nodeInfo . Node ( ) . Name
2025-07-24 07:48:07 -04:00
score , status := p . ( fwk . ScorePlugin ) . Score ( ctx , state , test . pod , nodeInfo )
2019-10-16 04:42:26 -04:00
if ! status . IsSuccess ( ) {
t . Errorf ( "unexpected error: %v" , status )
}
2025-07-24 07:48:07 -04:00
gotList = append ( gotList , fwk . NodeScore { Name : nodeName , Score : score } )
2019-10-16 04:42:26 -04:00
}
2025-07-24 07:48:07 -04:00
status = p . ( fwk . ScorePlugin ) . ScoreExtensions ( ) . NormalizeScore ( ctx , state , test . pod , gotList )
2019-10-16 04:42:26 -04:00
if ! status . IsSuccess ( ) {
t . Errorf ( "unexpected error: %v" , status )
}
2020-10-23 13:21:10 -04:00
if diff := cmp . Diff ( test . expectedList , gotList ) ; diff != "" {
t . Errorf ( "obtained scores (-want,+got):\n%s" , diff )
2019-10-16 04:42:26 -04:00
}
} )
}
}
2023-12-13 22:08:50 -05:00
func Test_isSchedulableAfterNodeChange ( t * testing . T ) {
2024-09-03 06:19:51 -04:00
podWithNodeAffinity := st . MakePod ( ) . NodeAffinityIn ( "foo" , [ ] string { "bar" } , st . NodeSelectorTypeMatchExpressions )
2023-12-13 22:08:50 -05:00
testcases := map [ string ] struct {
args * config . NodeAffinityArgs
pod * v1 . Pod
oldObj , newObj interface { }
2025-06-26 11:06:29 -04:00
expectedHint fwk . QueueingHint
2023-12-13 22:08:50 -05:00
expectedErr bool
} {
"backoff-wrong-new-object" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
newObj : "not-a-node" ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . Queue ,
2023-12-13 22:08:50 -05:00
expectedErr : true ,
} ,
"backoff-wrong-old-object" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
oldObj : "not-a-node" ,
newObj : st . MakeNode ( ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . Queue ,
2023-12-13 22:08:50 -05:00
expectedErr : true ,
} ,
"skip-queue-on-add" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
newObj : st . MakeNode ( ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . QueueSkip ,
2023-12-13 22:08:50 -05:00
} ,
"queue-on-add" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
newObj : st . MakeNode ( ) . Label ( "foo" , "bar" ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . Queue ,
2023-12-13 22:08:50 -05:00
} ,
"skip-unrelated-changes" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
oldObj : st . MakeNode ( ) . Obj ( ) ,
newObj : st . MakeNode ( ) . Capacity ( nil ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . QueueSkip ,
2023-12-13 22:08:50 -05:00
} ,
"skip-unrelated-changes-on-labels" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . DeepCopy ( ) ,
oldObj : st . MakeNode ( ) . Obj ( ) ,
newObj : st . MakeNode ( ) . Label ( "k" , "v" ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . QueueSkip ,
2023-12-13 22:08:50 -05:00
} ,
"skip-labels-changes-on-node-from-suitable-to-unsuitable" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . DeepCopy ( ) ,
oldObj : st . MakeNode ( ) . Label ( "foo" , "bar" ) . Obj ( ) ,
newObj : st . MakeNode ( ) . Label ( "k" , "v" ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . QueueSkip ,
2023-12-13 22:08:50 -05:00
} ,
"queue-on-labels-change-makes-pod-schedulable" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
oldObj : st . MakeNode ( ) . Obj ( ) ,
newObj : st . MakeNode ( ) . Label ( "foo" , "bar" ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . Queue ,
2023-12-13 22:08:50 -05:00
} ,
2024-09-18 09:36:52 -04:00
"skip-unrelated-change-that-keeps-pod-schedulable" : {
args : & config . NodeAffinityArgs { } ,
pod : podWithNodeAffinity . Obj ( ) ,
oldObj : st . MakeNode ( ) . Label ( "foo" , "bar" ) . Obj ( ) ,
newObj : st . MakeNode ( ) . Capacity ( nil ) . Label ( "foo" , "bar" ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . QueueSkip ,
2024-09-18 09:36:52 -04:00
} ,
2023-12-13 22:08:50 -05:00
"skip-queue-on-add-scheduler-enforced-node-affinity" : {
args : & config . NodeAffinityArgs {
AddedAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
pod : podWithNodeAffinity . Obj ( ) ,
newObj : st . MakeNode ( ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . QueueSkip ,
2023-12-13 22:08:50 -05:00
} ,
"queue-on-add-scheduler-enforced-node-affinity" : {
args : & config . NodeAffinityArgs {
AddedAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
Key : "foo" ,
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { "bar" } ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
pod : podWithNodeAffinity . Obj ( ) ,
newObj : st . MakeNode ( ) . Label ( "foo" , "bar" ) . Obj ( ) ,
2025-06-26 11:06:29 -04:00
expectedHint : fwk . Queue ,
2023-12-13 22:08:50 -05:00
} ,
}
for name , tc := range testcases {
t . Run ( name , func ( t * testing . T ) {
logger , ctx := ktesting . NewTestContext ( t )
2024-09-08 01:54:46 -04:00
p , err := New ( ctx , tc . args , nil , feature . Features { } )
2023-12-13 22:08:50 -05:00
if err != nil {
t . Fatalf ( "Creating plugin: %v" , err )
}
actualHint , err := p . ( * NodeAffinity ) . isSchedulableAfterNodeChange ( logger , tc . pod , tc . oldObj , tc . newObj )
if tc . expectedErr {
require . Error ( t , err )
return
}
require . NoError ( t , err )
require . Equal ( t , tc . expectedHint , actualHint )
} )
}
}