k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/podtopologyspread/plugin_test.go (about) 1 /* 2 Copyright 2023 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 podtopologyspread 18 19 import ( 20 "testing" 21 22 "github.com/stretchr/testify/require" 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/klog/v2/ktesting" 25 "k8s.io/kubernetes/pkg/scheduler/apis/config" 26 "k8s.io/kubernetes/pkg/scheduler/framework" 27 plugintesting "k8s.io/kubernetes/pkg/scheduler/framework/plugins/testing" 28 "k8s.io/kubernetes/pkg/scheduler/internal/cache" 29 st "k8s.io/kubernetes/pkg/scheduler/testing" 30 ) 31 32 func Test_isSchedulableAfterNodeChange(t *testing.T) { 33 testcases := []struct { 34 name string 35 pod *v1.Pod 36 oldNode, newNode *v1.Node 37 expectedHint framework.QueueingHint 38 expectedErr bool 39 }{ 40 { 41 name: "node updates label which matches topologyKey", 42 pod: st.MakePod().Name("p").SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 43 Obj(), 44 oldNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), 45 newNode: st.MakeNode().Name("node-a").Label("zone", "zone2").Obj(), 46 expectedHint: framework.Queue, 47 }, 48 { 49 name: "node that doesn't match topologySpreadConstraints updates non-related label", 50 pod: st.MakePod().Name("p").SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 51 Obj(), 52 oldNode: st.MakeNode().Name("node-a").Label("foo", "bar1").Obj(), 53 newNode: st.MakeNode().Name("node-a").Label("foo", "bar2").Obj(), 54 expectedHint: framework.QueueSkip, 55 }, 56 { 57 name: "node that match topologySpreadConstraints adds non-related label", 58 pod: st.MakePod().Name("p"). 59 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 60 SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 61 Obj(), 62 oldNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node1").Label("foo", "bar").Obj(), 63 newNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node1").Obj(), 64 expectedHint: framework.Queue, 65 }, 66 { 67 name: "create node with non-related labels", 68 pod: st.MakePod().Name("p").SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 69 Obj(), 70 newNode: st.MakeNode().Name("node-a").Label("foo", "bar").Obj(), 71 expectedHint: framework.QueueSkip, 72 }, 73 { 74 name: "create node with related labels", 75 pod: st.MakePod().Name("p").SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 76 Obj(), 77 newNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), 78 expectedHint: framework.Queue, 79 }, 80 { 81 name: "delete node with non-related labels", 82 pod: st.MakePod().Name("p").SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 83 Obj(), 84 oldNode: st.MakeNode().Name("node-a").Label("foo", "bar").Obj(), 85 expectedHint: framework.QueueSkip, 86 }, 87 { 88 name: "delete node with related labels", 89 pod: st.MakePod().Name("p").SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 90 Obj(), 91 oldNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), 92 expectedHint: framework.Queue, 93 }, 94 { 95 name: "add node with related labels that only match one of topologySpreadConstraints", 96 pod: st.MakePod().Name("p"). 97 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 98 SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 99 Obj(), 100 newNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), 101 expectedHint: framework.QueueSkip, 102 }, 103 { 104 name: "add node with related labels that match all topologySpreadConstraints", 105 pod: st.MakePod().Name("p"). 106 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 107 SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 108 Obj(), 109 newNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node1").Obj(), 110 expectedHint: framework.Queue, 111 }, 112 { 113 name: "update node with related labels that only match one of topologySpreadConstraints", 114 pod: st.MakePod().Name("p"). 115 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 116 SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 117 Obj(), 118 oldNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), 119 newNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), 120 expectedHint: framework.QueueSkip, 121 }, 122 { 123 name: "update node with related labels that match all topologySpreadConstraints", 124 pod: st.MakePod().Name("p"). 125 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 126 SpreadConstraint(1, "node", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 127 Obj(), 128 oldNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node1").Obj(), 129 newNode: st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node2").Obj(), 130 expectedHint: framework.Queue, 131 }, 132 } 133 134 for _, tc := range testcases { 135 t.Run(tc.name, func(t *testing.T) { 136 logger, ctx := ktesting.NewTestContext(t) 137 snapshot := cache.NewSnapshot(nil, nil) 138 pl := plugintesting.SetupPlugin(ctx, t, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, snapshot) 139 p := pl.(*PodTopologySpread) 140 actualHint, err := p.isSchedulableAfterNodeChange(logger, tc.pod, tc.oldNode, tc.newNode) 141 if tc.expectedErr { 142 require.Error(t, err) 143 return 144 } 145 require.NoError(t, err) 146 require.Equal(t, tc.expectedHint, actualHint) 147 }) 148 } 149 } 150 151 func Test_isSchedulableAfterPodChange(t *testing.T) { 152 testcases := []struct { 153 name string 154 pod *v1.Pod 155 oldPod, newPod *v1.Pod 156 expectedHint framework.QueueingHint 157 expectedErr bool 158 }{ 159 { 160 name: "add pod with labels match topologySpreadConstraints selector", 161 pod: st.MakePod().Name("p").Label("foo", ""). 162 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 163 Obj(), 164 newPod: st.MakePod().Node("fake-node").Label("foo", "").Obj(), 165 expectedHint: framework.Queue, 166 }, 167 { 168 name: "add un-scheduled pod", 169 pod: st.MakePod().Name("p").Label("foo", ""). 170 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 171 Obj(), 172 newPod: st.MakePod().Label("foo", "").Obj(), 173 expectedHint: framework.QueueSkip, 174 }, 175 { 176 name: "update un-scheduled pod", 177 pod: st.MakePod().Name("p").Label("foo", ""). 178 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 179 Obj(), 180 newPod: st.MakePod().Label("foo", "").Obj(), 181 oldPod: st.MakePod().Label("bar", "").Obj(), 182 expectedHint: framework.QueueSkip, 183 }, 184 { 185 name: "delete un-scheduled pod", 186 pod: st.MakePod().Name("p").Label("foo", ""). 187 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 188 Obj(), 189 oldPod: st.MakePod().Label("foo", "").Obj(), 190 expectedHint: framework.QueueSkip, 191 }, 192 { 193 name: "add pod with different namespace", 194 pod: st.MakePod().Name("p").Label("foo", ""). 195 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 196 Obj(), 197 newPod: st.MakePod().Node("fake-node").Namespace("fake-namespace").Label("foo", "").Obj(), 198 expectedHint: framework.QueueSkip, 199 }, 200 { 201 name: "add pod with labels don't match topologySpreadConstraints selector", 202 pod: st.MakePod().Name("p").Label("foo", ""). 203 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 204 Obj(), 205 newPod: st.MakePod().Node("fake-node").Label("bar", "").Obj(), 206 expectedHint: framework.QueueSkip, 207 }, 208 { 209 name: "delete pod with labels that match topologySpreadConstraints selector", 210 pod: st.MakePod().Name("p").Label("foo", ""). 211 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 212 Obj(), 213 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Obj(), 214 expectedHint: framework.Queue, 215 }, 216 { 217 name: "delete pod with labels that don't match topologySpreadConstraints selector", 218 pod: st.MakePod().Name("p").Label("foo", ""). 219 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 220 Obj(), 221 oldPod: st.MakePod().Node("fake-node").Label("bar", "").Obj(), 222 expectedHint: framework.QueueSkip, 223 }, 224 { 225 name: "update pod's non-related label", 226 pod: st.MakePod().Name("p").Label("foo", ""). 227 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 228 Obj(), 229 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "bar1").Obj(), 230 newPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "bar2").Obj(), 231 expectedHint: framework.QueueSkip, 232 }, 233 { 234 name: "add pod's label that matches topologySpreadConstraints selector", 235 pod: st.MakePod().Name("p").Label("foo", ""). 236 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 237 Obj(), 238 oldPod: st.MakePod().Node("fake-node").Obj(), 239 newPod: st.MakePod().Node("fake-node").Label("foo", "").Obj(), 240 expectedHint: framework.Queue, 241 }, 242 { 243 name: "delete pod label that matches topologySpreadConstraints selector", 244 pod: st.MakePod().Name("p").Label("foo", ""). 245 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 246 Obj(), 247 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Obj(), 248 newPod: st.MakePod().Node("fake-node").Obj(), 249 expectedHint: framework.Queue, 250 }, 251 { 252 name: "change pod's label that matches topologySpreadConstraints selector", 253 pod: st.MakePod().Name("p").Label("foo", ""). 254 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 255 Obj(), 256 oldPod: st.MakePod().Node("fake-node").Label("foo", "foo1").Obj(), 257 newPod: st.MakePod().Node("fake-node").Label("foo", "foo2").Obj(), 258 expectedHint: framework.QueueSkip, 259 }, 260 { 261 name: "change pod's label that doesn't match topologySpreadConstraints selector", 262 pod: st.MakePod().Name("p").Label("foo", ""). 263 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 264 Obj(), 265 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "bar1").Obj(), 266 newPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "bar2").Obj(), 267 expectedHint: framework.QueueSkip, 268 }, 269 { 270 name: "add pod's label that matches topologySpreadConstraints selector with multi topologySpreadConstraints", 271 pod: st.MakePod().Name("p").Label("foo", ""). 272 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 273 SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil). 274 Obj(), 275 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Obj(), 276 newPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "bar2").Obj(), 277 expectedHint: framework.Queue, 278 }, 279 { 280 name: "change pod's label that doesn't match topologySpreadConstraints selector with multi topologySpreadConstraints", 281 pod: st.MakePod().Name("p").Label("foo", ""). 282 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 283 SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil). 284 Obj(), 285 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Obj(), 286 newPod: st.MakePod().Node("fake-node").Label("foo", "").Label("baz", "").Obj(), 287 expectedHint: framework.QueueSkip, 288 }, 289 { 290 name: "change pod's label that match topologySpreadConstraints selector with multi topologySpreadConstraints", 291 pod: st.MakePod().Name("p").Label("foo", ""). 292 SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). 293 SpreadConstraint(1, "node", v1.DoNotSchedule, barSelector, nil, nil, nil, nil). 294 Obj(), 295 oldPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "").Obj(), 296 newPod: st.MakePod().Node("fake-node").Label("foo", "").Label("bar", "bar2").Obj(), 297 expectedHint: framework.QueueSkip, 298 }, 299 } 300 for _, tc := range testcases { 301 t.Run(tc.name, func(t *testing.T) { 302 logger, ctx := ktesting.NewTestContext(t) 303 snapshot := cache.NewSnapshot(nil, nil) 304 pl := plugintesting.SetupPlugin(ctx, t, topologySpreadFunc, &config.PodTopologySpreadArgs{DefaultingType: config.ListDefaulting}, snapshot) 305 p := pl.(*PodTopologySpread) 306 actualHint, err := p.isSchedulableAfterPodChange(logger, tc.pod, tc.oldPod, tc.newPod) 307 if tc.expectedErr { 308 require.Error(t, err) 309 return 310 } 311 require.NoError(t, err) 312 require.Equal(t, tc.expectedHint, actualHint) 313 }) 314 } 315 }