k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/interpodaffinity/plugin_test.go (about)

     1  /*
     2  Copyright 2024 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package interpodaffinity
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/klog/v2/ktesting"
    27  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    28  	"k8s.io/kubernetes/pkg/scheduler/framework"
    29  	plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing"
    30  	"k8s.io/kubernetes/pkg/scheduler/internal/cache"
    31  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    32  )
    33  
    34  func Test_isSchedulableAfterPodChange(t *testing.T) {
    35  	tests := []struct {
    36  		name           string
    37  		pod            *v1.Pod
    38  		oldPod, newPod *v1.Pod
    39  		expectedHint   framework.QueueingHint
    40  	}{
    41  		{
    42  			name:         "add a pod which matches the pod affinity",
    43  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    44  			newPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
    45  			expectedHint: framework.Queue,
    46  		},
    47  		{
    48  			name:         "add an un-scheduled pod",
    49  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    50  			newPod:       st.MakePod().Label("service", "securityscan").Obj(),
    51  			expectedHint: framework.QueueSkip,
    52  		},
    53  		{
    54  			name:         "add a pod which doesn't match the pod affinity",
    55  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    56  			newPod:       st.MakePod().Node("fake-node").Label("aaa", "a").Obj(),
    57  			expectedHint: framework.QueueSkip,
    58  		},
    59  		{
    60  			name:         "update a pod from non-match to match pod affinity",
    61  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    62  			newPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
    63  			oldPod:       st.MakePod().Node("fake-node").Label("aaa", "a").Obj(),
    64  			expectedHint: framework.Queue,
    65  		},
    66  		{
    67  			name:         "the updating pod matches target pod's affinity both before and after label changes",
    68  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    69  			newPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
    70  			oldPod:       st.MakePod().Node("fake-node").Label("service", "value2").Obj(),
    71  			expectedHint: framework.QueueSkip,
    72  		},
    73  		{
    74  			name:         "update an un-scheduled pod",
    75  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    76  			newPod:       st.MakePod().Label("service", "securityscan").Obj(),
    77  			oldPod:       st.MakePod().Label("service", "securityscan").Obj(),
    78  			expectedHint: framework.QueueSkip,
    79  		},
    80  		{
    81  			name:         "update a pod from match to non-match the pod affinity",
    82  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
    83  			newPod:       st.MakePod().Node("fake-node").Label("aaa", "a").Obj(),
    84  			oldPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
    85  			expectedHint: framework.QueueSkip,
    86  		},
    87  		{
    88  			name: "update a pod from match to non-match pod's affinity - multiple terms case",
    89  			pod: st.MakePod().Name("p").PodAffinityExists("aaa", "hostname", st.PodAffinityWithRequiredReq).
    90  				PodAffinityExists("bbb", "hostname", st.PodAffinityWithRequiredReq).Obj(),
    91  			newPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
    92  			oldPod:       st.MakePod().Node("fake-node").Label("aaa", "a").Obj(),
    93  			expectedHint: framework.QueueSkip,
    94  		},
    95  		{
    96  			name: "update a pod from non-match to match pod's affinity - multiple terms case",
    97  			pod: st.MakePod().Name("p").PodAffinityExists("aaa", "hostname", st.PodAffinityWithRequiredReq).
    98  				PodAffinityExists("bbb", "hostname", st.PodAffinityWithRequiredReq).Obj(),
    99  			newPod:       st.MakePod().Node("fake-node").Label("aaa", "").Label("bbb", "").Obj(),
   100  			oldPod:       st.MakePod().Node("fake-node").Label("aaa", "a").Obj(),
   101  			expectedHint: framework.Queue,
   102  		},
   103  		{
   104  			name:         "modify pod label to change it from satisfying pod anti-affinity to not satisfying anti-affinity",
   105  			pod:          st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(),
   106  			newPod:       st.MakePod().Node("fake-node").Label("service", "aaaa").Obj(),
   107  			oldPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
   108  			expectedHint: framework.Queue,
   109  		},
   110  		{
   111  			name:         "modify pod label to change it from not satisfying pod anti-affinity to satisfying anti-affinity",
   112  			pod:          st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(),
   113  			newPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
   114  			oldPod:       st.MakePod().Node("fake-node").Label("service", "bbb").Obj(),
   115  			expectedHint: framework.QueueSkip,
   116  		},
   117  		{
   118  			name:         "delete a pod which doesn't match pod's anti-affinity",
   119  			pod:          st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(),
   120  			oldPod:       st.MakePod().Node("fake-node").Label("aaa", "a").Obj(),
   121  			expectedHint: framework.QueueSkip,
   122  		},
   123  		{
   124  			name:         "delete a pod which matches pod's anti-affinity",
   125  			pod:          st.MakePod().Name("p").PodAntiAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAntiAffinityWithRequiredReq).Obj(),
   126  			oldPod:       st.MakePod().Node("fake-node").Label("service", "securityscan").Obj(),
   127  			expectedHint: framework.Queue,
   128  		},
   129  	}
   130  	for _, tc := range tests {
   131  		t.Run(tc.name, func(t *testing.T) {
   132  			logger, ctx := ktesting.NewTestContext(t)
   133  			ctx, cancel := context.WithCancel(ctx)
   134  			defer cancel()
   135  
   136  			snapshot := cache.NewSnapshot(nil, nil)
   137  			pl := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot, namespaces)
   138  			p := pl.(*InterPodAffinity)
   139  			actualHint, err := p.isSchedulableAfterPodChange(logger, tc.pod, tc.oldPod, tc.newPod)
   140  			if err != nil {
   141  				t.Errorf("unexpected error: %v", err)
   142  			}
   143  			if diff := cmp.Diff(tc.expectedHint, actualHint); diff != "" {
   144  				t.Errorf("expected QueuingHint doesn't match (-want,+got): \n %s", diff)
   145  			}
   146  		})
   147  	}
   148  }
   149  
   150  func Test_isSchedulableAfterNodeChange(t *testing.T) {
   151  	testcases := []struct {
   152  		name             string
   153  		pod              *v1.Pod
   154  		oldNode, newNode *v1.Node
   155  		expectedHint     framework.QueueingHint
   156  	}{
   157  		{
   158  			name:         "add a new node with matched pod affinity topologyKey",
   159  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "zone", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
   160  			newNode:      st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(),
   161  			expectedHint: framework.Queue,
   162  		},
   163  		{
   164  			name:         "add a new node with matched pod anti-affinity topologyKey",
   165  			pod:          st.MakePod().Name("p").PodAntiAffinity("zone", &metav1.LabelSelector{MatchLabels: map[string]string{"another": "label"}}, st.PodAntiAffinityWithRequiredReq).Obj(),
   166  			newNode:      st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(),
   167  			expectedHint: framework.Queue,
   168  		},
   169  		{
   170  			name:         "add a new node without matched topologyKey",
   171  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "region", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
   172  			newNode:      st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(),
   173  			expectedHint: framework.QueueSkip,
   174  		},
   175  		{
   176  			name:         "update node topologyKey",
   177  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "zone", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
   178  			oldNode:      st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(),
   179  			newNode:      st.MakeNode().Name("node-a").Label("zone", "zone2").Obj(),
   180  			expectedHint: framework.Queue,
   181  		},
   182  		{
   183  			name:         "update node lable but not topologyKey",
   184  			pod:          st.MakePod().Name("p").PodAffinityIn("service", "zone", []string{"securityscan", "value2"}, st.PodAffinityWithRequiredReq).Obj(),
   185  			oldNode:      st.MakeNode().Name("node-a").Label("aaa", "a").Obj(),
   186  			newNode:      st.MakeNode().Name("node-a").Label("aaa", "b").Obj(),
   187  			expectedHint: framework.QueueSkip,
   188  		},
   189  	}
   190  	for _, tc := range testcases {
   191  		t.Run(tc.name, func(t *testing.T) {
   192  			logger, ctx := ktesting.NewTestContext(t)
   193  			ctx, cancel := context.WithCancel(ctx)
   194  			defer cancel()
   195  
   196  			snapshot := cache.NewSnapshot(nil, nil)
   197  			pl := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{}, snapshot, namespaces)
   198  			p := pl.(*InterPodAffinity)
   199  			actualHint, err := p.isSchedulableAfterNodeChange(logger, tc.pod, tc.oldNode, tc.newNode)
   200  			if err != nil {
   201  				t.Errorf("unexpected error: %v", err)
   202  			}
   203  			if diff := cmp.Diff(tc.expectedHint, actualHint); diff != "" {
   204  				t.Errorf("expected QueuingHint doesn't match (-want,+got): \n %s", diff)
   205  			}
   206  		})
   207  	}
   208  }