k8s.io/kubernetes@v1.29.3/pkg/scheduler/internal/cache/snapshot_test.go (about) 1 /* 2 Copyright 2018 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 cache 18 19 import ( 20 "fmt" 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/apimachinery/pkg/labels" 27 "k8s.io/apimachinery/pkg/util/sets" 28 "k8s.io/kubernetes/pkg/scheduler/framework" 29 st "k8s.io/kubernetes/pkg/scheduler/testing" 30 ) 31 32 const mb int64 = 1024 * 1024 33 34 func TestGetNodeImageStates(t *testing.T) { 35 tests := []struct { 36 node *v1.Node 37 imageExistenceMap map[string]sets.Set[string] 38 expected map[string]*framework.ImageStateSummary 39 }{ 40 { 41 node: &v1.Node{ 42 ObjectMeta: metav1.ObjectMeta{Name: "node-0"}, 43 Status: v1.NodeStatus{ 44 Images: []v1.ContainerImage{ 45 { 46 Names: []string{ 47 "gcr.io/10:v1", 48 }, 49 SizeBytes: int64(10 * mb), 50 }, 51 { 52 Names: []string{ 53 "gcr.io/200:v1", 54 }, 55 SizeBytes: int64(200 * mb), 56 }, 57 }, 58 }, 59 }, 60 imageExistenceMap: map[string]sets.Set[string]{ 61 "gcr.io/10:v1": sets.New("node-0", "node-1"), 62 "gcr.io/200:v1": sets.New("node-0"), 63 }, 64 expected: map[string]*framework.ImageStateSummary{ 65 "gcr.io/10:v1": { 66 Size: int64(10 * mb), 67 NumNodes: 2, 68 }, 69 "gcr.io/200:v1": { 70 Size: int64(200 * mb), 71 NumNodes: 1, 72 }, 73 }, 74 }, 75 { 76 node: &v1.Node{ 77 ObjectMeta: metav1.ObjectMeta{Name: "node-0"}, 78 Status: v1.NodeStatus{}, 79 }, 80 imageExistenceMap: map[string]sets.Set[string]{ 81 "gcr.io/10:v1": sets.New("node-1"), 82 "gcr.io/200:v1": sets.New[string](), 83 }, 84 expected: map[string]*framework.ImageStateSummary{}, 85 }, 86 } 87 88 for i, test := range tests { 89 t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { 90 imageStates := getNodeImageStates(test.node, test.imageExistenceMap) 91 if diff := cmp.Diff(test.expected, imageStates); diff != "" { 92 t.Errorf("Unexpected imageStates (-want, +got):\n%s", diff) 93 } 94 }) 95 } 96 } 97 98 func TestCreateImageExistenceMap(t *testing.T) { 99 tests := []struct { 100 nodes []*v1.Node 101 expected map[string]sets.Set[string] 102 }{ 103 { 104 nodes: []*v1.Node{ 105 { 106 ObjectMeta: metav1.ObjectMeta{Name: "node-0"}, 107 Status: v1.NodeStatus{ 108 Images: []v1.ContainerImage{ 109 { 110 Names: []string{ 111 "gcr.io/10:v1", 112 }, 113 SizeBytes: int64(10 * mb), 114 }, 115 }, 116 }, 117 }, 118 { 119 ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, 120 Status: v1.NodeStatus{ 121 Images: []v1.ContainerImage{ 122 { 123 Names: []string{ 124 "gcr.io/10:v1", 125 }, 126 SizeBytes: int64(10 * mb), 127 }, 128 { 129 Names: []string{ 130 "gcr.io/200:v1", 131 }, 132 SizeBytes: int64(200 * mb), 133 }, 134 }, 135 }, 136 }, 137 }, 138 expected: map[string]sets.Set[string]{ 139 "gcr.io/10:v1": sets.New("node-0", "node-1"), 140 "gcr.io/200:v1": sets.New("node-1"), 141 }, 142 }, 143 { 144 nodes: []*v1.Node{ 145 { 146 ObjectMeta: metav1.ObjectMeta{Name: "node-0"}, 147 Status: v1.NodeStatus{}, 148 }, 149 { 150 ObjectMeta: metav1.ObjectMeta{Name: "node-1"}, 151 Status: v1.NodeStatus{ 152 Images: []v1.ContainerImage{ 153 { 154 Names: []string{ 155 "gcr.io/10:v1", 156 }, 157 SizeBytes: int64(10 * mb), 158 }, 159 { 160 Names: []string{ 161 "gcr.io/200:v1", 162 }, 163 SizeBytes: int64(200 * mb), 164 }, 165 }, 166 }, 167 }, 168 }, 169 expected: map[string]sets.Set[string]{ 170 "gcr.io/10:v1": sets.New("node-1"), 171 "gcr.io/200:v1": sets.New("node-1"), 172 }, 173 }, 174 } 175 176 for i, test := range tests { 177 t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { 178 imageMap := createImageExistenceMap(test.nodes) 179 if diff := cmp.Diff(test.expected, imageMap); diff != "" { 180 t.Errorf("Unexpected imageMap (-want, +got):\n%s", diff) 181 } 182 }) 183 } 184 } 185 186 func TestCreateUsedPVCSet(t *testing.T) { 187 tests := []struct { 188 name string 189 pods []*v1.Pod 190 expected sets.Set[string] 191 }{ 192 { 193 name: "empty pods list", 194 pods: []*v1.Pod{}, 195 expected: sets.New[string](), 196 }, 197 { 198 name: "pods not scheduled", 199 pods: []*v1.Pod{ 200 st.MakePod().Name("foo").Namespace("foo").Obj(), 201 st.MakePod().Name("bar").Namespace("bar").Obj(), 202 }, 203 expected: sets.New[string](), 204 }, 205 { 206 name: "scheduled pods that do not use any PVC", 207 pods: []*v1.Pod{ 208 st.MakePod().Name("foo").Namespace("foo").Node("node-1").Obj(), 209 st.MakePod().Name("bar").Namespace("bar").Node("node-2").Obj(), 210 }, 211 expected: sets.New[string](), 212 }, 213 { 214 name: "scheduled pods that use PVC", 215 pods: []*v1.Pod{ 216 st.MakePod().Name("foo").Namespace("foo").Node("node-1").PVC("pvc1").Obj(), 217 st.MakePod().Name("bar").Namespace("bar").Node("node-2").PVC("pvc2").Obj(), 218 }, 219 expected: sets.New("foo/pvc1", "bar/pvc2"), 220 }, 221 } 222 223 for _, test := range tests { 224 t.Run(test.name, func(t *testing.T) { 225 usedPVCs := createUsedPVCSet(test.pods) 226 if diff := cmp.Diff(test.expected, usedPVCs); diff != "" { 227 t.Errorf("Unexpected usedPVCs (-want +got):\n%s", diff) 228 } 229 }) 230 } 231 } 232 233 func TestNewSnapshot(t *testing.T) { 234 podWithAnnotations := st.MakePod().Name("foo").Namespace("ns").Node("node-1").Annotations(map[string]string{"custom": "annotation"}).Obj() 235 podWithPort := st.MakePod().Name("foo").Namespace("foo").Node("node-0").ContainerPort([]v1.ContainerPort{{HostPort: 8080}}).Obj() 236 podWithAntiAffitiny := st.MakePod().Name("baz").Namespace("ns").PodAntiAffinity("another", &metav1.LabelSelector{MatchLabels: map[string]string{"another": "label"}}, st.PodAntiAffinityWithRequiredReq).Node("node-0").Obj() 237 podsWithAffitiny := []*v1.Pod{ 238 st.MakePod().Name("bar").Namespace("ns").PodAffinity("baz", &metav1.LabelSelector{MatchLabels: map[string]string{"baz": "qux"}}, st.PodAffinityWithRequiredReq).Node("node-2").Obj(), 239 st.MakePod().Name("bar").Namespace("ns").PodAffinity("key", &metav1.LabelSelector{MatchLabels: map[string]string{"key": "value"}}, st.PodAffinityWithRequiredReq).Node("node-0").Obj(), 240 } 241 podsWithPVCs := []*v1.Pod{ 242 st.MakePod().Name("foo").Namespace("foo").Node("node-0").PVC("pvc0").Obj(), 243 st.MakePod().Name("bar").Namespace("bar").Node("node-1").PVC("pvc1").Obj(), 244 st.MakePod().Name("baz").Namespace("baz").Node("node-2").PVC("pvc2").Obj(), 245 } 246 testCases := []struct { 247 name string 248 pods []*v1.Pod 249 nodes []*v1.Node 250 expectedNodesInfos []*framework.NodeInfo 251 expectedNumNodes int 252 expectedPodsWithAffinity int 253 expectedPodsWithAntiAffinity int 254 expectedUsedPVCSet sets.Set[string] 255 }{ 256 { 257 name: "no pods no nodes", 258 pods: nil, 259 nodes: nil, 260 }, 261 { 262 name: "single pod single node", 263 pods: []*v1.Pod{ 264 podWithPort, 265 }, 266 nodes: []*v1.Node{ 267 {ObjectMeta: metav1.ObjectMeta{Name: "node-0"}}, 268 }, 269 expectedNodesInfos: []*framework.NodeInfo{ 270 { 271 Pods: []*framework.PodInfo{ 272 {Pod: podWithPort}, 273 }, 274 }, 275 }, 276 expectedNumNodes: 1, 277 }, 278 { 279 name: "multiple nodes, pods with PVCs", 280 pods: podsWithPVCs, 281 nodes: []*v1.Node{ 282 {ObjectMeta: metav1.ObjectMeta{Name: "node-0"}}, 283 {ObjectMeta: metav1.ObjectMeta{Name: "node-1"}}, 284 {ObjectMeta: metav1.ObjectMeta{Name: "node-2"}}, 285 }, 286 expectedNodesInfos: []*framework.NodeInfo{ 287 { 288 Pods: []*framework.PodInfo{ 289 {Pod: podsWithPVCs[0]}, 290 }, 291 }, 292 { 293 Pods: []*framework.PodInfo{ 294 {Pod: podsWithPVCs[1]}, 295 }, 296 }, 297 { 298 Pods: []*framework.PodInfo{ 299 {Pod: podsWithPVCs[2]}, 300 }, 301 }, 302 }, 303 expectedNumNodes: 3, 304 expectedUsedPVCSet: sets.New("foo/pvc0", "bar/pvc1", "baz/pvc2"), 305 }, 306 { 307 name: "multiple nodes, pod with affinity", 308 pods: []*v1.Pod{ 309 podWithAnnotations, 310 podsWithAffitiny[0], 311 }, 312 nodes: []*v1.Node{ 313 {ObjectMeta: metav1.ObjectMeta{Name: "node-0"}}, 314 {ObjectMeta: metav1.ObjectMeta{Name: "node-1"}}, 315 {ObjectMeta: metav1.ObjectMeta{Name: "node-2", Labels: map[string]string{"baz": "qux"}}}, 316 }, 317 expectedNodesInfos: []*framework.NodeInfo{ 318 { 319 Pods: []*framework.PodInfo{}, 320 }, 321 { 322 Pods: []*framework.PodInfo{ 323 {Pod: podWithAnnotations}, 324 }, 325 }, 326 { 327 Pods: []*framework.PodInfo{ 328 { 329 Pod: podsWithAffitiny[0], 330 RequiredAffinityTerms: []framework.AffinityTerm{ 331 { 332 Namespaces: sets.New("ns"), 333 Selector: labels.SelectorFromSet(map[string]string{"baz": "qux"}), 334 TopologyKey: "baz", 335 NamespaceSelector: labels.Nothing(), 336 }, 337 }, 338 }, 339 }, 340 }, 341 }, 342 expectedNumNodes: 3, 343 expectedPodsWithAffinity: 1, 344 }, 345 { 346 name: "multiple nodes, pod with affinity, pod with anti-affinity", 347 pods: []*v1.Pod{ 348 podsWithAffitiny[1], 349 podWithAntiAffitiny, 350 }, 351 nodes: []*v1.Node{ 352 {ObjectMeta: metav1.ObjectMeta{Name: "node-0", Labels: map[string]string{"key": "value"}}}, 353 {ObjectMeta: metav1.ObjectMeta{Name: "node-1", Labels: map[string]string{"another": "label"}}}, 354 }, 355 expectedNodesInfos: []*framework.NodeInfo{ 356 { 357 Pods: []*framework.PodInfo{ 358 { 359 Pod: podsWithAffitiny[1], 360 RequiredAffinityTerms: []framework.AffinityTerm{ 361 { 362 Namespaces: sets.New("ns"), 363 Selector: labels.SelectorFromSet(map[string]string{"key": "value"}), 364 TopologyKey: "key", 365 NamespaceSelector: labels.Nothing(), 366 }, 367 }, 368 }, 369 { 370 Pod: podWithAntiAffitiny, 371 RequiredAntiAffinityTerms: []framework.AffinityTerm{ 372 { 373 Namespaces: sets.New("ns"), 374 Selector: labels.SelectorFromSet(map[string]string{"another": "label"}), 375 TopologyKey: "another", 376 NamespaceSelector: labels.Nothing(), 377 }, 378 }, 379 }, 380 }, 381 }, 382 { 383 Pods: []*framework.PodInfo{}, 384 }, 385 }, 386 expectedNumNodes: 2, 387 expectedPodsWithAffinity: 1, 388 expectedPodsWithAntiAffinity: 1, 389 }, 390 } 391 392 for _, test := range testCases { 393 t.Run(test.name, func(t *testing.T) { 394 snapshot := NewSnapshot(test.pods, test.nodes) 395 396 if test.expectedNumNodes != snapshot.NumNodes() { 397 t.Errorf("unexpected number of nodes, want: %v, got: %v", test.expectedNumNodes, snapshot.NumNodes()) 398 } 399 400 for i, node := range test.nodes { 401 info, err := snapshot.Get(node.Name) 402 if err != nil { 403 t.Errorf("unexpected error but got %s", err) 404 } 405 if info == nil { 406 t.Error("node infos should not be nil") 407 } 408 for j := range test.expectedNodesInfos[i].Pods { 409 if diff := cmp.Diff(test.expectedNodesInfos[i].Pods[j], info.Pods[j]); diff != "" { 410 t.Errorf("Unexpected PodInfo (-want +got):\n%s", diff) 411 } 412 } 413 } 414 415 affinityList, err := snapshot.HavePodsWithAffinityList() 416 if err != nil { 417 t.Errorf("unexpected error but got %s", err) 418 } 419 if test.expectedPodsWithAffinity != len(affinityList) { 420 t.Errorf("unexpected affinityList number, want: %v, got: %v", test.expectedPodsWithAffinity, len(affinityList)) 421 } 422 423 antiAffinityList, err := snapshot.HavePodsWithRequiredAntiAffinityList() 424 if err != nil { 425 t.Errorf("unexpected error but got %s", err) 426 } 427 if test.expectedPodsWithAntiAffinity != len(antiAffinityList) { 428 t.Errorf("unexpected antiAffinityList number, want: %v, got: %v", test.expectedPodsWithAntiAffinity, len(antiAffinityList)) 429 } 430 431 for key := range test.expectedUsedPVCSet { 432 if !snapshot.IsPVCUsedByPods(key) { 433 t.Errorf("unexpected IsPVCUsedByPods for %s, want: true, got: false", key) 434 } 435 } 436 437 if diff := cmp.Diff(test.expectedUsedPVCSet, snapshot.usedPVCSet); diff != "" { 438 t.Errorf("Unexpected usedPVCSet (-want +got):\n%s", diff) 439 } 440 }) 441 } 442 }