k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/devicemanager/topology_hints_test.go (about) 1 /* 2 Copyright 2019 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 devicemanager 18 19 import ( 20 "fmt" 21 "reflect" 22 "sort" 23 "testing" 24 25 v1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/util/sets" 29 pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" 30 "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager" 31 "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" 32 ) 33 34 type mockAffinityStore struct { 35 hint topologymanager.TopologyHint 36 } 37 38 func (m *mockAffinityStore) GetAffinity(podUID string, containerName string) topologymanager.TopologyHint { 39 return m.hint 40 } 41 42 func (m *mockAffinityStore) GetPolicy() topologymanager.Policy { 43 return nil 44 } 45 46 func makeNUMADevice(id string, numa int) pluginapi.Device { 47 return pluginapi.Device{ 48 ID: id, 49 Topology: &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{{ID: int64(numa)}}}, 50 } 51 } 52 53 func makeSocketMask(sockets ...int) bitmask.BitMask { 54 mask, _ := bitmask.NewBitMask(sockets...) 55 return mask 56 } 57 58 func TestGetTopologyHints(t *testing.T) { 59 tcases := getCommonTestCases() 60 61 for _, tc := range tcases { 62 m := ManagerImpl{ 63 allDevices: NewResourceDeviceInstances(), 64 healthyDevices: make(map[string]sets.Set[string]), 65 allocatedDevices: make(map[string]sets.Set[string]), 66 podDevices: newPodDevices(), 67 sourcesReady: &sourcesReadyStub{}, 68 activePods: func() []*v1.Pod { return []*v1.Pod{tc.pod} }, 69 numaNodes: []int{0, 1}, 70 } 71 72 for r := range tc.devices { 73 m.allDevices[r] = make(DeviceInstances) 74 m.healthyDevices[r] = sets.New[string]() 75 76 for _, d := range tc.devices[r] { 77 m.allDevices[r][d.ID] = d 78 m.healthyDevices[r].Insert(d.ID) 79 } 80 } 81 82 for p := range tc.allocatedDevices { 83 for c := range tc.allocatedDevices[p] { 84 for r, devices := range tc.allocatedDevices[p][c] { 85 m.podDevices.insert(p, c, r, constructDevices(devices), nil) 86 87 m.allocatedDevices[r] = sets.New[string]() 88 for _, d := range devices { 89 m.allocatedDevices[r].Insert(d) 90 } 91 } 92 } 93 } 94 95 hints := m.GetTopologyHints(tc.pod, &tc.pod.Spec.Containers[0]) 96 97 for r := range tc.expectedHints { 98 sort.SliceStable(hints[r], func(i, j int) bool { 99 return hints[r][i].LessThan(hints[r][j]) 100 }) 101 sort.SliceStable(tc.expectedHints[r], func(i, j int) bool { 102 return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j]) 103 }) 104 if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) { 105 t.Errorf("%v: Expected result to be %#v, got %#v", tc.description, tc.expectedHints[r], hints[r]) 106 } 107 } 108 } 109 } 110 111 func TestTopologyAlignedAllocation(t *testing.T) { 112 tcases := []struct { 113 description string 114 resource string 115 request int 116 devices []pluginapi.Device 117 allocatedDevices []string 118 hint topologymanager.TopologyHint 119 getPreferredAllocationFunc func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) 120 expectedPreferredAllocation []string 121 expectedAlignment map[int]int 122 }{ 123 { 124 description: "Single Request, no alignment", 125 resource: "resource", 126 request: 1, 127 devices: []pluginapi.Device{ 128 {ID: "Dev1"}, 129 {ID: "Dev2"}, 130 }, 131 hint: topologymanager.TopologyHint{ 132 NUMANodeAffinity: makeSocketMask(0, 1), 133 Preferred: true, 134 }, 135 expectedAlignment: map[int]int{}, 136 }, 137 { 138 description: "Request for 1, partial alignment", 139 resource: "resource", 140 request: 1, 141 devices: []pluginapi.Device{ 142 {ID: "Dev1"}, 143 makeNUMADevice("Dev2", 1), 144 }, 145 hint: topologymanager.TopologyHint{ 146 NUMANodeAffinity: makeSocketMask(1), 147 Preferred: true, 148 }, 149 expectedAlignment: map[int]int{1: 1}, 150 }, 151 { 152 description: "Single Request, socket 0", 153 resource: "resource", 154 request: 1, 155 devices: []pluginapi.Device{ 156 makeNUMADevice("Dev1", 0), 157 makeNUMADevice("Dev2", 1), 158 }, 159 hint: topologymanager.TopologyHint{ 160 NUMANodeAffinity: makeSocketMask(0), 161 Preferred: true, 162 }, 163 expectedAlignment: map[int]int{0: 1}, 164 }, 165 { 166 description: "Single Request, socket 1", 167 resource: "resource", 168 request: 1, 169 devices: []pluginapi.Device{ 170 makeNUMADevice("Dev1", 0), 171 makeNUMADevice("Dev2", 1), 172 }, 173 hint: topologymanager.TopologyHint{ 174 NUMANodeAffinity: makeSocketMask(1), 175 Preferred: true, 176 }, 177 expectedAlignment: map[int]int{1: 1}, 178 }, 179 { 180 description: "Request for 2, socket 0", 181 resource: "resource", 182 request: 2, 183 devices: []pluginapi.Device{ 184 makeNUMADevice("Dev1", 0), 185 makeNUMADevice("Dev2", 1), 186 makeNUMADevice("Dev3", 0), 187 makeNUMADevice("Dev4", 1), 188 }, 189 hint: topologymanager.TopologyHint{ 190 NUMANodeAffinity: makeSocketMask(0), 191 Preferred: true, 192 }, 193 expectedAlignment: map[int]int{0: 2}, 194 }, 195 { 196 description: "Request for 2, socket 1", 197 resource: "resource", 198 request: 2, 199 devices: []pluginapi.Device{ 200 makeNUMADevice("Dev1", 0), 201 makeNUMADevice("Dev2", 1), 202 makeNUMADevice("Dev3", 0), 203 makeNUMADevice("Dev4", 1), 204 }, 205 hint: topologymanager.TopologyHint{ 206 NUMANodeAffinity: makeSocketMask(1), 207 Preferred: true, 208 }, 209 expectedAlignment: map[int]int{1: 2}, 210 }, 211 { 212 description: "Request for 4, unsatisfiable, prefer socket 0", 213 resource: "resource", 214 request: 4, 215 devices: []pluginapi.Device{ 216 makeNUMADevice("Dev1", 0), 217 makeNUMADevice("Dev2", 1), 218 makeNUMADevice("Dev3", 0), 219 makeNUMADevice("Dev4", 1), 220 makeNUMADevice("Dev5", 0), 221 makeNUMADevice("Dev6", 1), 222 }, 223 hint: topologymanager.TopologyHint{ 224 NUMANodeAffinity: makeSocketMask(0), 225 Preferred: true, 226 }, 227 expectedAlignment: map[int]int{0: 3, 1: 1}, 228 }, 229 { 230 description: "Request for 4, unsatisfiable, prefer socket 1", 231 resource: "resource", 232 request: 4, 233 devices: []pluginapi.Device{ 234 makeNUMADevice("Dev1", 0), 235 makeNUMADevice("Dev2", 1), 236 makeNUMADevice("Dev3", 0), 237 makeNUMADevice("Dev4", 1), 238 makeNUMADevice("Dev5", 0), 239 makeNUMADevice("Dev6", 1), 240 }, 241 hint: topologymanager.TopologyHint{ 242 NUMANodeAffinity: makeSocketMask(1), 243 Preferred: true, 244 }, 245 expectedAlignment: map[int]int{0: 1, 1: 3}, 246 }, 247 { 248 description: "Request for 4, multisocket", 249 resource: "resource", 250 request: 4, 251 devices: []pluginapi.Device{ 252 makeNUMADevice("Dev1", 0), 253 makeNUMADevice("Dev2", 1), 254 makeNUMADevice("Dev3", 2), 255 makeNUMADevice("Dev4", 3), 256 makeNUMADevice("Dev5", 0), 257 makeNUMADevice("Dev6", 1), 258 makeNUMADevice("Dev7", 2), 259 makeNUMADevice("Dev8", 3), 260 }, 261 hint: topologymanager.TopologyHint{ 262 NUMANodeAffinity: makeSocketMask(1, 3), 263 Preferred: true, 264 }, 265 expectedAlignment: map[int]int{1: 2, 3: 2}, 266 }, 267 { 268 description: "Request for 5, socket 0, preferred aligned accepted", 269 resource: "resource", 270 request: 5, 271 devices: func() []pluginapi.Device { 272 devices := []pluginapi.Device{} 273 for i := 0; i < 100; i++ { 274 id := fmt.Sprintf("Dev%d", i) 275 devices = append(devices, makeNUMADevice(id, 0)) 276 } 277 for i := 100; i < 200; i++ { 278 id := fmt.Sprintf("Dev%d", i) 279 devices = append(devices, makeNUMADevice(id, 1)) 280 } 281 return devices 282 }(), 283 hint: topologymanager.TopologyHint{ 284 NUMANodeAffinity: makeSocketMask(0), 285 Preferred: true, 286 }, 287 getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) { 288 return &pluginapi.PreferredAllocationResponse{ 289 ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{ 290 {DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "Dev42", "Dev77"}}, 291 }, 292 }, nil 293 }, 294 expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83", "Dev42", "Dev77"}, 295 expectedAlignment: map[int]int{0: 5}, 296 }, 297 { 298 description: "Request for 5, socket 0, preferred aligned accepted, unaligned ignored", 299 resource: "resource", 300 request: 5, 301 devices: func() []pluginapi.Device { 302 devices := []pluginapi.Device{} 303 for i := 0; i < 100; i++ { 304 id := fmt.Sprintf("Dev%d", i) 305 devices = append(devices, makeNUMADevice(id, 0)) 306 } 307 for i := 100; i < 200; i++ { 308 id := fmt.Sprintf("Dev%d", i) 309 devices = append(devices, makeNUMADevice(id, 1)) 310 } 311 return devices 312 }(), 313 hint: topologymanager.TopologyHint{ 314 NUMANodeAffinity: makeSocketMask(0), 315 Preferred: true, 316 }, 317 getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) { 318 return &pluginapi.PreferredAllocationResponse{ 319 ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{ 320 {DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "Dev150", "Dev186"}}, 321 }, 322 }, nil 323 }, 324 expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83"}, 325 expectedAlignment: map[int]int{0: 5}, 326 }, 327 { 328 description: "Request for 5, socket 1, preferred aligned accepted, bogus ignored", 329 resource: "resource", 330 request: 5, 331 devices: func() []pluginapi.Device { 332 devices := []pluginapi.Device{} 333 for i := 0; i < 100; i++ { 334 id := fmt.Sprintf("Dev%d", i) 335 devices = append(devices, makeNUMADevice(id, 1)) 336 } 337 return devices 338 }(), 339 hint: topologymanager.TopologyHint{ 340 NUMANodeAffinity: makeSocketMask(1), 341 Preferred: true, 342 }, 343 getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) { 344 return &pluginapi.PreferredAllocationResponse{ 345 ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{ 346 {DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "bogus0", "bogus1"}}, 347 }, 348 }, nil 349 }, 350 expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83"}, 351 expectedAlignment: map[int]int{1: 5}, 352 }, 353 { 354 description: "Request for 5, multisocket, preferred accepted", 355 resource: "resource", 356 request: 5, 357 devices: func() []pluginapi.Device { 358 devices := []pluginapi.Device{} 359 for i := 0; i < 3; i++ { 360 id := fmt.Sprintf("Dev%d", i) 361 devices = append(devices, makeNUMADevice(id, 0)) 362 } 363 for i := 3; i < 100; i++ { 364 id := fmt.Sprintf("Dev%d", i) 365 devices = append(devices, makeNUMADevice(id, 1)) 366 } 367 return devices 368 }(), 369 hint: topologymanager.TopologyHint{ 370 NUMANodeAffinity: makeSocketMask(0), 371 Preferred: true, 372 }, 373 getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) { 374 return &pluginapi.PreferredAllocationResponse{ 375 ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{ 376 {DeviceIDs: []string{"Dev0", "Dev1", "Dev2", "Dev42", "Dev83"}}, 377 }, 378 }, nil 379 }, 380 expectedPreferredAllocation: []string{"Dev0", "Dev1", "Dev2", "Dev42", "Dev83"}, 381 expectedAlignment: map[int]int{0: 3, 1: 2}, 382 }, 383 { 384 description: "Request for 5, multisocket, preferred unaligned accepted, bogus ignored", 385 resource: "resource", 386 request: 5, 387 devices: func() []pluginapi.Device { 388 devices := []pluginapi.Device{} 389 for i := 0; i < 3; i++ { 390 id := fmt.Sprintf("Dev%d", i) 391 devices = append(devices, makeNUMADevice(id, 0)) 392 } 393 for i := 3; i < 100; i++ { 394 id := fmt.Sprintf("Dev%d", i) 395 devices = append(devices, makeNUMADevice(id, 1)) 396 } 397 return devices 398 }(), 399 hint: topologymanager.TopologyHint{ 400 NUMANodeAffinity: makeSocketMask(0), 401 Preferred: true, 402 }, 403 getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) { 404 return &pluginapi.PreferredAllocationResponse{ 405 ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{ 406 {DeviceIDs: []string{"Dev0", "Dev1", "Dev2", "Dev42", "bogus0"}}, 407 }, 408 }, nil 409 }, 410 expectedPreferredAllocation: []string{"Dev0", "Dev1", "Dev2", "Dev42"}, 411 expectedAlignment: map[int]int{0: 3, 1: 2}, 412 }, 413 } 414 for _, tc := range tcases { 415 m := ManagerImpl{ 416 allDevices: NewResourceDeviceInstances(), 417 healthyDevices: make(map[string]sets.Set[string]), 418 allocatedDevices: make(map[string]sets.Set[string]), 419 endpoints: make(map[string]endpointInfo), 420 podDevices: newPodDevices(), 421 sourcesReady: &sourcesReadyStub{}, 422 activePods: func() []*v1.Pod { return []*v1.Pod{} }, 423 topologyAffinityStore: &mockAffinityStore{tc.hint}, 424 } 425 426 m.allDevices[tc.resource] = make(DeviceInstances) 427 m.healthyDevices[tc.resource] = sets.New[string]() 428 m.endpoints[tc.resource] = endpointInfo{} 429 430 for _, d := range tc.devices { 431 m.allDevices[tc.resource][d.ID] = d 432 m.healthyDevices[tc.resource].Insert(d.ID) 433 } 434 435 if tc.getPreferredAllocationFunc != nil { 436 m.endpoints[tc.resource] = endpointInfo{ 437 e: &MockEndpoint{ 438 getPreferredAllocationFunc: tc.getPreferredAllocationFunc, 439 }, 440 opts: &pluginapi.DevicePluginOptions{GetPreferredAllocationAvailable: true}, 441 } 442 } 443 444 allocated, err := m.devicesToAllocate("podUID", "containerName", tc.resource, tc.request, sets.New[string]()) 445 if err != nil { 446 t.Errorf("Unexpected error: %v", err) 447 continue 448 } 449 450 if len(allocated) != tc.request { 451 t.Errorf("%v. expected allocation size: %v but got: %v", tc.description, tc.request, len(allocated)) 452 } 453 454 if !allocated.HasAll(tc.expectedPreferredAllocation...) { 455 t.Errorf("%v. expected preferred allocation: %v but not present in: %v", tc.description, tc.expectedPreferredAllocation, allocated.UnsortedList()) 456 } 457 458 alignment := make(map[int]int) 459 if m.deviceHasTopologyAlignment(tc.resource) { 460 for d := range allocated { 461 if m.allDevices[tc.resource][d].Topology != nil { 462 alignment[int(m.allDevices[tc.resource][d].Topology.Nodes[0].ID)]++ 463 } 464 } 465 } 466 467 if !reflect.DeepEqual(alignment, tc.expectedAlignment) { 468 t.Errorf("%v. expected alignment: %v but got: %v", tc.description, tc.expectedAlignment, alignment) 469 } 470 } 471 } 472 473 func TestGetPreferredAllocationParameters(t *testing.T) { 474 tcases := []struct { 475 description string 476 resource string 477 request int 478 allDevices []pluginapi.Device 479 allocatedDevices []string 480 reusableDevices []string 481 hint topologymanager.TopologyHint 482 expectedAvailable []string 483 expectedMustInclude []string 484 expectedSize int 485 }{ 486 { 487 description: "Request for 1, socket 0, 0 already allocated, 0 reusable", 488 resource: "resource", 489 request: 1, 490 allDevices: []pluginapi.Device{ 491 makeNUMADevice("Dev0", 0), 492 makeNUMADevice("Dev1", 0), 493 makeNUMADevice("Dev2", 0), 494 makeNUMADevice("Dev3", 0), 495 }, 496 allocatedDevices: []string{}, 497 reusableDevices: []string{}, 498 hint: topologymanager.TopologyHint{ 499 NUMANodeAffinity: makeSocketMask(0), 500 Preferred: true, 501 }, 502 expectedAvailable: []string{"Dev0", "Dev1", "Dev2", "Dev3"}, 503 expectedMustInclude: []string{}, 504 expectedSize: 1, 505 }, 506 { 507 description: "Request for 4, socket 0, 2 already allocated, 2 reusable", 508 resource: "resource", 509 request: 4, 510 allDevices: []pluginapi.Device{ 511 makeNUMADevice("Dev0", 0), 512 makeNUMADevice("Dev1", 0), 513 makeNUMADevice("Dev2", 0), 514 makeNUMADevice("Dev3", 0), 515 makeNUMADevice("Dev4", 0), 516 makeNUMADevice("Dev5", 0), 517 makeNUMADevice("Dev6", 0), 518 makeNUMADevice("Dev7", 0), 519 }, 520 allocatedDevices: []string{"Dev0", "Dev5"}, 521 reusableDevices: []string{"Dev0", "Dev5"}, 522 hint: topologymanager.TopologyHint{ 523 NUMANodeAffinity: makeSocketMask(0), 524 Preferred: true, 525 }, 526 expectedAvailable: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6", "Dev7"}, 527 expectedMustInclude: []string{"Dev0", "Dev5"}, 528 expectedSize: 4, 529 }, 530 { 531 description: "Request for 4, socket 0, 4 already allocated, 2 reusable", 532 resource: "resource", 533 request: 4, 534 allDevices: []pluginapi.Device{ 535 makeNUMADevice("Dev0", 0), 536 makeNUMADevice("Dev1", 0), 537 makeNUMADevice("Dev2", 0), 538 makeNUMADevice("Dev3", 0), 539 makeNUMADevice("Dev4", 0), 540 makeNUMADevice("Dev5", 0), 541 makeNUMADevice("Dev6", 0), 542 makeNUMADevice("Dev7", 0), 543 }, 544 allocatedDevices: []string{"Dev0", "Dev5", "Dev4", "Dev1"}, 545 reusableDevices: []string{"Dev0", "Dev5"}, 546 hint: topologymanager.TopologyHint{ 547 NUMANodeAffinity: makeSocketMask(0), 548 Preferred: true, 549 }, 550 expectedAvailable: []string{"Dev0", "Dev2", "Dev3", "Dev5", "Dev6", "Dev7"}, 551 expectedMustInclude: []string{"Dev0", "Dev5"}, 552 expectedSize: 4, 553 }, 554 { 555 description: "Request for 6, multisocket, 2 already allocated, 2 reusable", 556 resource: "resource", 557 request: 6, 558 allDevices: []pluginapi.Device{ 559 makeNUMADevice("Dev0", 0), 560 makeNUMADevice("Dev1", 0), 561 makeNUMADevice("Dev2", 0), 562 makeNUMADevice("Dev3", 0), 563 makeNUMADevice("Dev4", 1), 564 makeNUMADevice("Dev5", 1), 565 makeNUMADevice("Dev6", 1), 566 makeNUMADevice("Dev7", 1), 567 }, 568 allocatedDevices: []string{"Dev1", "Dev6"}, 569 reusableDevices: []string{"Dev1", "Dev6"}, 570 hint: topologymanager.TopologyHint{ 571 NUMANodeAffinity: makeSocketMask(0), 572 Preferred: true, 573 }, 574 expectedAvailable: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6", "Dev7"}, 575 expectedMustInclude: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev6"}, 576 expectedSize: 6, 577 }, 578 { 579 description: "Request for 6, multisocket, 4 already allocated, 2 reusable", 580 resource: "resource", 581 request: 6, 582 allDevices: []pluginapi.Device{ 583 makeNUMADevice("Dev0", 0), 584 makeNUMADevice("Dev1", 0), 585 makeNUMADevice("Dev2", 0), 586 makeNUMADevice("Dev3", 0), 587 makeNUMADevice("Dev4", 1), 588 makeNUMADevice("Dev5", 1), 589 makeNUMADevice("Dev6", 1), 590 makeNUMADevice("Dev7", 1), 591 }, 592 allocatedDevices: []string{"Dev0", "Dev1", "Dev6", "Dev7"}, 593 reusableDevices: []string{"Dev1", "Dev6"}, 594 hint: topologymanager.TopologyHint{ 595 NUMANodeAffinity: makeSocketMask(0), 596 Preferred: true, 597 }, 598 expectedAvailable: []string{"Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6"}, 599 expectedMustInclude: []string{"Dev1", "Dev2", "Dev3", "Dev6"}, 600 expectedSize: 6, 601 }, 602 } 603 for _, tc := range tcases { 604 m := ManagerImpl{ 605 allDevices: NewResourceDeviceInstances(), 606 healthyDevices: make(map[string]sets.Set[string]), 607 allocatedDevices: make(map[string]sets.Set[string]), 608 endpoints: make(map[string]endpointInfo), 609 podDevices: newPodDevices(), 610 sourcesReady: &sourcesReadyStub{}, 611 activePods: func() []*v1.Pod { return []*v1.Pod{} }, 612 topologyAffinityStore: &mockAffinityStore{tc.hint}, 613 } 614 615 m.allDevices[tc.resource] = make(DeviceInstances) 616 m.healthyDevices[tc.resource] = sets.New[string]() 617 for _, d := range tc.allDevices { 618 m.allDevices[tc.resource][d.ID] = d 619 m.healthyDevices[tc.resource].Insert(d.ID) 620 } 621 622 m.allocatedDevices[tc.resource] = sets.New[string]() 623 for _, d := range tc.allocatedDevices { 624 m.allocatedDevices[tc.resource].Insert(d) 625 } 626 627 actualAvailable := []string{} 628 actualMustInclude := []string{} 629 actualSize := 0 630 m.endpoints[tc.resource] = endpointInfo{ 631 e: &MockEndpoint{ 632 getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) { 633 actualAvailable = append(actualAvailable, available...) 634 actualMustInclude = append(actualMustInclude, mustInclude...) 635 actualSize = size 636 return nil, nil 637 }, 638 }, 639 opts: &pluginapi.DevicePluginOptions{GetPreferredAllocationAvailable: true}, 640 } 641 642 _, err := m.devicesToAllocate("podUID", "containerName", tc.resource, tc.request, sets.New[string](tc.reusableDevices...)) 643 if err != nil { 644 t.Errorf("Unexpected error: %v", err) 645 continue 646 } 647 648 if !sets.New[string](actualAvailable...).Equal(sets.New[string](tc.expectedAvailable...)) { 649 t.Errorf("%v. expected available: %v but got: %v", tc.description, tc.expectedAvailable, actualAvailable) 650 } 651 652 if !sets.New[string](actualAvailable...).Equal(sets.New[string](tc.expectedAvailable...)) { 653 t.Errorf("%v. expected mustInclude: %v but got: %v", tc.description, tc.expectedMustInclude, actualMustInclude) 654 } 655 656 if actualSize != tc.expectedSize { 657 t.Errorf("%v. expected size: %v but got: %v", tc.description, tc.expectedSize, actualSize) 658 } 659 } 660 } 661 662 func TestGetPodDeviceRequest(t *testing.T) { 663 tcases := []struct { 664 description string 665 pod *v1.Pod 666 registeredDevices []string 667 expected map[string]int 668 }{ 669 { 670 description: "empty pod", 671 pod: &v1.Pod{}, 672 registeredDevices: []string{}, 673 expected: map[string]int{}, 674 }, 675 { 676 description: "Init container requests device plugin resource", 677 pod: &v1.Pod{ 678 Spec: v1.PodSpec{ 679 InitContainers: []v1.Container{ 680 { 681 Resources: v1.ResourceRequirements{ 682 Limits: v1.ResourceList{ 683 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 684 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 685 v1.ResourceName("gpu"): resource.MustParse("2"), 686 }, 687 }, 688 }, 689 }, 690 }, 691 }, 692 registeredDevices: []string{"gpu"}, 693 expected: map[string]int{"gpu": 2}, 694 }, 695 { 696 description: "Init containers request device plugin resource", 697 pod: &v1.Pod{ 698 Spec: v1.PodSpec{ 699 InitContainers: []v1.Container{ 700 { 701 Resources: v1.ResourceRequirements{ 702 Limits: v1.ResourceList{ 703 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 704 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 705 v1.ResourceName("gpu"): resource.MustParse("2"), 706 }, 707 }, 708 }, 709 { 710 Resources: v1.ResourceRequirements{ 711 Limits: v1.ResourceList{ 712 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 713 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 714 v1.ResourceName("gpu"): resource.MustParse("4"), 715 }, 716 }, 717 }, 718 }, 719 }, 720 }, 721 registeredDevices: []string{"gpu"}, 722 expected: map[string]int{"gpu": 4}, 723 }, 724 { 725 description: "User container requests device plugin resource", 726 pod: &v1.Pod{ 727 Spec: v1.PodSpec{ 728 Containers: []v1.Container{ 729 { 730 Resources: v1.ResourceRequirements{ 731 Limits: v1.ResourceList{ 732 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 733 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 734 v1.ResourceName("gpu"): resource.MustParse("2"), 735 }, 736 }, 737 }, 738 }, 739 }, 740 }, 741 registeredDevices: []string{"gpu"}, 742 expected: map[string]int{"gpu": 2}, 743 }, 744 { 745 description: "Init containers and user containers request the same amount of device plugin resources", 746 pod: &v1.Pod{ 747 Spec: v1.PodSpec{ 748 InitContainers: []v1.Container{ 749 { 750 Resources: v1.ResourceRequirements{ 751 Limits: v1.ResourceList{ 752 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 753 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 754 v1.ResourceName("gpu"): resource.MustParse("2"), 755 v1.ResourceName("nic"): resource.MustParse("2"), 756 }, 757 }, 758 }, 759 { 760 Resources: v1.ResourceRequirements{ 761 Limits: v1.ResourceList{ 762 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 763 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 764 v1.ResourceName("gpu"): resource.MustParse("2"), 765 v1.ResourceName("nic"): resource.MustParse("2"), 766 }, 767 }, 768 }, 769 }, 770 Containers: []v1.Container{ 771 { 772 Resources: v1.ResourceRequirements{ 773 Limits: v1.ResourceList{ 774 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 775 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 776 v1.ResourceName("gpu"): resource.MustParse("1"), 777 v1.ResourceName("nic"): resource.MustParse("1"), 778 }, 779 }, 780 }, 781 { 782 Resources: v1.ResourceRequirements{ 783 Limits: v1.ResourceList{ 784 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 785 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 786 v1.ResourceName("gpu"): resource.MustParse("1"), 787 v1.ResourceName("nic"): resource.MustParse("1"), 788 }, 789 }, 790 }, 791 }, 792 }, 793 }, 794 registeredDevices: []string{"gpu", "nic"}, 795 expected: map[string]int{"gpu": 2, "nic": 2}, 796 }, 797 { 798 description: "Init containers request more device plugin resources than user containers", 799 pod: &v1.Pod{ 800 Spec: v1.PodSpec{ 801 InitContainers: []v1.Container{ 802 { 803 Resources: v1.ResourceRequirements{ 804 Limits: v1.ResourceList{ 805 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 806 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 807 v1.ResourceName("gpu"): resource.MustParse("2"), 808 v1.ResourceName("nic"): resource.MustParse("1"), 809 }, 810 }, 811 }, 812 { 813 Resources: v1.ResourceRequirements{ 814 Limits: v1.ResourceList{ 815 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 816 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 817 v1.ResourceName("gpu"): resource.MustParse("3"), 818 v1.ResourceName("nic"): resource.MustParse("2"), 819 }, 820 }, 821 }, 822 }, 823 Containers: []v1.Container{ 824 { 825 Resources: v1.ResourceRequirements{ 826 Limits: v1.ResourceList{ 827 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 828 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 829 v1.ResourceName("gpu"): resource.MustParse("1"), 830 v1.ResourceName("nic"): resource.MustParse("1"), 831 }, 832 }, 833 }, 834 { 835 Resources: v1.ResourceRequirements{ 836 Limits: v1.ResourceList{ 837 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 838 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 839 v1.ResourceName("gpu"): resource.MustParse("1"), 840 }, 841 }, 842 }, 843 }, 844 }, 845 }, 846 registeredDevices: []string{"gpu", "nic"}, 847 expected: map[string]int{"gpu": 3, "nic": 2}, 848 }, 849 { 850 description: "User containers request more device plugin resources than init containers", 851 pod: &v1.Pod{ 852 Spec: v1.PodSpec{ 853 InitContainers: []v1.Container{ 854 { 855 Resources: v1.ResourceRequirements{ 856 Limits: v1.ResourceList{ 857 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 858 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 859 v1.ResourceName("gpu"): resource.MustParse("2"), 860 v1.ResourceName("nic"): resource.MustParse("1"), 861 }, 862 }, 863 }, 864 { 865 Resources: v1.ResourceRequirements{ 866 Limits: v1.ResourceList{ 867 v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"), 868 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 869 v1.ResourceName("gpu"): resource.MustParse("2"), 870 v1.ResourceName("nic"): resource.MustParse("1"), 871 }, 872 }, 873 }, 874 }, 875 Containers: []v1.Container{ 876 { 877 Resources: v1.ResourceRequirements{ 878 Limits: v1.ResourceList{ 879 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 880 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 881 v1.ResourceName("gpu"): resource.MustParse("3"), 882 v1.ResourceName("nic"): resource.MustParse("2"), 883 }, 884 }, 885 }, 886 { 887 Resources: v1.ResourceRequirements{ 888 Limits: v1.ResourceList{ 889 v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"), 890 v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"), 891 v1.ResourceName("gpu"): resource.MustParse("3"), 892 v1.ResourceName("nic"): resource.MustParse("2"), 893 }, 894 }, 895 }, 896 }, 897 }, 898 }, 899 registeredDevices: []string{"gpu", "nic"}, 900 expected: map[string]int{"gpu": 6, "nic": 4}, 901 }, 902 } 903 904 for _, tc := range tcases { 905 m := ManagerImpl{ 906 healthyDevices: make(map[string]sets.Set[string]), 907 } 908 909 for _, res := range tc.registeredDevices { 910 m.healthyDevices[res] = sets.New[string]() 911 } 912 913 accumulatedResourceRequests := m.getPodDeviceRequest(tc.pod) 914 915 if !reflect.DeepEqual(accumulatedResourceRequests, tc.expected) { 916 t.Errorf("%v. expected alignment: %v but got: %v", tc.description, tc.expected, accumulatedResourceRequests) 917 } 918 } 919 } 920 921 func TestGetPodTopologyHints(t *testing.T) { 922 tcases := getCommonTestCases() 923 tcases = append(tcases, getPodScopeTestCases()...) 924 925 for _, tc := range tcases { 926 m := ManagerImpl{ 927 allDevices: NewResourceDeviceInstances(), 928 healthyDevices: make(map[string]sets.Set[string]), 929 allocatedDevices: make(map[string]sets.Set[string]), 930 podDevices: newPodDevices(), 931 sourcesReady: &sourcesReadyStub{}, 932 activePods: func() []*v1.Pod { return []*v1.Pod{tc.pod, {ObjectMeta: metav1.ObjectMeta{UID: "fakeOtherPod"}}} }, 933 numaNodes: []int{0, 1}, 934 } 935 936 for r := range tc.devices { 937 m.allDevices[r] = make(DeviceInstances) 938 m.healthyDevices[r] = sets.New[string]() 939 940 for _, d := range tc.devices[r] { 941 //add `pluginapi.Device` with Topology 942 m.allDevices[r][d.ID] = d 943 m.healthyDevices[r].Insert(d.ID) 944 } 945 } 946 947 for p := range tc.allocatedDevices { 948 for c := range tc.allocatedDevices[p] { 949 for r, devices := range tc.allocatedDevices[p][c] { 950 m.podDevices.insert(p, c, r, constructDevices(devices), nil) 951 952 m.allocatedDevices[r] = sets.New[string]() 953 for _, d := range devices { 954 m.allocatedDevices[r].Insert(d) 955 } 956 } 957 } 958 } 959 960 hints := m.GetPodTopologyHints(tc.pod) 961 962 for r := range tc.expectedHints { 963 sort.SliceStable(hints[r], func(i, j int) bool { 964 return hints[r][i].LessThan(hints[r][j]) 965 }) 966 sort.SliceStable(tc.expectedHints[r], func(i, j int) bool { 967 return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j]) 968 }) 969 if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) { 970 t.Errorf("%v: Expected result to be %v, got %v", tc.description, tc.expectedHints[r], hints[r]) 971 } 972 } 973 } 974 } 975 976 type topologyHintTestCase struct { 977 description string 978 pod *v1.Pod 979 devices map[string][]pluginapi.Device 980 allocatedDevices map[string]map[string]map[string][]string 981 expectedHints map[string][]topologymanager.TopologyHint 982 } 983 984 func getCommonTestCases() []topologyHintTestCase { 985 return []topologyHintTestCase{ 986 { 987 description: "Single Request, no alignment", 988 pod: &v1.Pod{ 989 ObjectMeta: metav1.ObjectMeta{ 990 UID: "fakePod", 991 }, 992 Spec: v1.PodSpec{ 993 Containers: []v1.Container{ 994 { 995 Name: "fakeContainer", 996 Resources: v1.ResourceRequirements{ 997 Limits: v1.ResourceList{ 998 v1.ResourceName("testdevice"): resource.MustParse("1"), 999 }, 1000 }, 1001 }, 1002 }, 1003 }, 1004 }, 1005 devices: map[string][]pluginapi.Device{ 1006 "testdevice": { 1007 {ID: "Dev1"}, 1008 {ID: "Dev2"}, 1009 {ID: "Dev3", Topology: &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{}}}, 1010 {ID: "Dev4", Topology: &pluginapi.TopologyInfo{Nodes: nil}}, 1011 }, 1012 }, 1013 expectedHints: map[string][]topologymanager.TopologyHint{ 1014 "testdevice": nil, 1015 }, 1016 }, 1017 { 1018 description: "Single Request, only one with alignment", 1019 pod: &v1.Pod{ 1020 ObjectMeta: metav1.ObjectMeta{ 1021 UID: "fakePod", 1022 }, 1023 Spec: v1.PodSpec{ 1024 Containers: []v1.Container{ 1025 { 1026 Name: "fakeContainer", 1027 Resources: v1.ResourceRequirements{ 1028 Limits: v1.ResourceList{ 1029 v1.ResourceName("testdevice"): resource.MustParse("1"), 1030 }, 1031 }, 1032 }, 1033 }, 1034 }, 1035 }, 1036 devices: map[string][]pluginapi.Device{ 1037 "testdevice": { 1038 {ID: "Dev1"}, 1039 makeNUMADevice("Dev2", 1), 1040 }, 1041 }, 1042 expectedHints: map[string][]topologymanager.TopologyHint{ 1043 "testdevice": { 1044 { 1045 NUMANodeAffinity: makeSocketMask(1), 1046 Preferred: true, 1047 }, 1048 { 1049 NUMANodeAffinity: makeSocketMask(0, 1), 1050 Preferred: false, 1051 }, 1052 }, 1053 }, 1054 }, 1055 { 1056 description: "Single Request, one device per socket", 1057 pod: &v1.Pod{ 1058 ObjectMeta: metav1.ObjectMeta{ 1059 UID: "fakePod", 1060 }, 1061 Spec: v1.PodSpec{ 1062 Containers: []v1.Container{ 1063 { 1064 Name: "fakeContainer", 1065 Resources: v1.ResourceRequirements{ 1066 Limits: v1.ResourceList{ 1067 v1.ResourceName("testdevice"): resource.MustParse("1"), 1068 }, 1069 }, 1070 }, 1071 }, 1072 }, 1073 }, 1074 devices: map[string][]pluginapi.Device{ 1075 "testdevice": { 1076 makeNUMADevice("Dev1", 0), 1077 makeNUMADevice("Dev2", 1), 1078 }, 1079 }, 1080 expectedHints: map[string][]topologymanager.TopologyHint{ 1081 "testdevice": { 1082 { 1083 NUMANodeAffinity: makeSocketMask(0), 1084 Preferred: true, 1085 }, 1086 { 1087 NUMANodeAffinity: makeSocketMask(1), 1088 Preferred: true, 1089 }, 1090 { 1091 NUMANodeAffinity: makeSocketMask(0, 1), 1092 Preferred: false, 1093 }, 1094 }, 1095 }, 1096 }, 1097 { 1098 description: "Request for 2, one device per socket", 1099 pod: &v1.Pod{ 1100 ObjectMeta: metav1.ObjectMeta{ 1101 UID: "fakePod", 1102 }, 1103 Spec: v1.PodSpec{ 1104 Containers: []v1.Container{ 1105 { 1106 Name: "fakeContainer", 1107 Resources: v1.ResourceRequirements{ 1108 Limits: v1.ResourceList{ 1109 v1.ResourceName("testdevice"): resource.MustParse("2"), 1110 }, 1111 }, 1112 }, 1113 }, 1114 }, 1115 }, 1116 devices: map[string][]pluginapi.Device{ 1117 "testdevice": { 1118 makeNUMADevice("Dev1", 0), 1119 makeNUMADevice("Dev2", 1), 1120 }, 1121 }, 1122 expectedHints: map[string][]topologymanager.TopologyHint{ 1123 "testdevice": { 1124 { 1125 NUMANodeAffinity: makeSocketMask(0, 1), 1126 Preferred: true, 1127 }, 1128 }, 1129 }, 1130 }, 1131 { 1132 description: "Request for 2, 2 devices per socket", 1133 pod: &v1.Pod{ 1134 ObjectMeta: metav1.ObjectMeta{ 1135 UID: "fakePod", 1136 }, 1137 Spec: v1.PodSpec{ 1138 Containers: []v1.Container{ 1139 { 1140 Name: "fakeContainer", 1141 Resources: v1.ResourceRequirements{ 1142 Limits: v1.ResourceList{ 1143 v1.ResourceName("testdevice"): resource.MustParse("2"), 1144 }, 1145 }, 1146 }, 1147 }, 1148 }, 1149 }, 1150 devices: map[string][]pluginapi.Device{ 1151 "testdevice": { 1152 makeNUMADevice("Dev1", 0), 1153 makeNUMADevice("Dev2", 1), 1154 makeNUMADevice("Dev3", 0), 1155 makeNUMADevice("Dev4", 1), 1156 }, 1157 }, 1158 expectedHints: map[string][]topologymanager.TopologyHint{ 1159 "testdevice": { 1160 { 1161 NUMANodeAffinity: makeSocketMask(0), 1162 Preferred: true, 1163 }, 1164 { 1165 NUMANodeAffinity: makeSocketMask(1), 1166 Preferred: true, 1167 }, 1168 { 1169 NUMANodeAffinity: makeSocketMask(0, 1), 1170 Preferred: false, 1171 }, 1172 }, 1173 }, 1174 }, 1175 { 1176 description: "Request for 2, optimal on 1 NUMA node, forced cross-NUMA", 1177 pod: &v1.Pod{ 1178 ObjectMeta: metav1.ObjectMeta{ 1179 UID: "fakePod", 1180 }, 1181 Spec: v1.PodSpec{ 1182 Containers: []v1.Container{ 1183 { 1184 Name: "fakeContainer", 1185 Resources: v1.ResourceRequirements{ 1186 Limits: v1.ResourceList{ 1187 v1.ResourceName("testdevice"): resource.MustParse("2"), 1188 }, 1189 }, 1190 }, 1191 }, 1192 }, 1193 }, 1194 devices: map[string][]pluginapi.Device{ 1195 "testdevice": { 1196 makeNUMADevice("Dev1", 0), 1197 makeNUMADevice("Dev2", 1), 1198 makeNUMADevice("Dev3", 0), 1199 makeNUMADevice("Dev4", 1), 1200 }, 1201 }, 1202 allocatedDevices: map[string]map[string]map[string][]string{ 1203 "fakePod": { 1204 "fakeOtherContainer": { 1205 "testdevice": {"Dev1", "Dev2"}, 1206 }, 1207 }, 1208 }, 1209 expectedHints: map[string][]topologymanager.TopologyHint{ 1210 "testdevice": { 1211 { 1212 NUMANodeAffinity: makeSocketMask(0, 1), 1213 Preferred: false, 1214 }, 1215 }, 1216 }, 1217 }, 1218 { 1219 description: "2 device types, mixed configuration", 1220 pod: &v1.Pod{ 1221 ObjectMeta: metav1.ObjectMeta{ 1222 UID: "fakePod", 1223 }, 1224 Spec: v1.PodSpec{ 1225 Containers: []v1.Container{ 1226 { 1227 Name: "fakeContainer", 1228 Resources: v1.ResourceRequirements{ 1229 Limits: v1.ResourceList{ 1230 v1.ResourceName("testdevice1"): resource.MustParse("2"), 1231 v1.ResourceName("testdevice2"): resource.MustParse("1"), 1232 }, 1233 }, 1234 }, 1235 }, 1236 }, 1237 }, 1238 devices: map[string][]pluginapi.Device{ 1239 "testdevice1": { 1240 makeNUMADevice("Dev1", 0), 1241 makeNUMADevice("Dev2", 1), 1242 makeNUMADevice("Dev3", 0), 1243 makeNUMADevice("Dev4", 1), 1244 }, 1245 "testdevice2": { 1246 makeNUMADevice("Dev1", 0), 1247 }, 1248 }, 1249 expectedHints: map[string][]topologymanager.TopologyHint{ 1250 "testdevice1": { 1251 { 1252 NUMANodeAffinity: makeSocketMask(0), 1253 Preferred: true, 1254 }, 1255 { 1256 NUMANodeAffinity: makeSocketMask(1), 1257 Preferred: true, 1258 }, 1259 { 1260 NUMANodeAffinity: makeSocketMask(0, 1), 1261 Preferred: false, 1262 }, 1263 }, 1264 "testdevice2": { 1265 { 1266 NUMANodeAffinity: makeSocketMask(0), 1267 Preferred: true, 1268 }, 1269 { 1270 NUMANodeAffinity: makeSocketMask(0, 1), 1271 Preferred: false, 1272 }, 1273 }, 1274 }, 1275 }, 1276 { 1277 description: "Single device type, more requested than available", 1278 pod: &v1.Pod{ 1279 ObjectMeta: metav1.ObjectMeta{ 1280 UID: "fakePod", 1281 }, 1282 Spec: v1.PodSpec{ 1283 Containers: []v1.Container{ 1284 { 1285 Name: "fakeContainer", 1286 Resources: v1.ResourceRequirements{ 1287 Limits: v1.ResourceList{ 1288 v1.ResourceName("testdevice"): resource.MustParse("6"), 1289 }, 1290 }, 1291 }, 1292 }, 1293 }, 1294 }, 1295 devices: map[string][]pluginapi.Device{ 1296 "testdevice": { 1297 makeNUMADevice("Dev1", 0), 1298 makeNUMADevice("Dev2", 0), 1299 makeNUMADevice("Dev3", 1), 1300 makeNUMADevice("Dev4", 1), 1301 }, 1302 }, 1303 expectedHints: map[string][]topologymanager.TopologyHint{ 1304 "testdevice": {}, 1305 }, 1306 }, 1307 { 1308 description: "Single device type, all already allocated to container", 1309 pod: &v1.Pod{ 1310 ObjectMeta: metav1.ObjectMeta{ 1311 UID: "fakePod", 1312 }, 1313 Spec: v1.PodSpec{ 1314 Containers: []v1.Container{ 1315 { 1316 Name: "fakeContainer", 1317 Resources: v1.ResourceRequirements{ 1318 Limits: v1.ResourceList{ 1319 v1.ResourceName("testdevice"): resource.MustParse("2"), 1320 }, 1321 }, 1322 }, 1323 }, 1324 }, 1325 }, 1326 devices: map[string][]pluginapi.Device{ 1327 "testdevice": { 1328 makeNUMADevice("Dev1", 0), 1329 makeNUMADevice("Dev2", 0), 1330 }, 1331 }, 1332 allocatedDevices: map[string]map[string]map[string][]string{ 1333 "fakePod": { 1334 "fakeContainer": { 1335 "testdevice": {"Dev1", "Dev2"}, 1336 }, 1337 }, 1338 }, 1339 expectedHints: map[string][]topologymanager.TopologyHint{ 1340 "testdevice": { 1341 { 1342 NUMANodeAffinity: makeSocketMask(0), 1343 Preferred: true, 1344 }, 1345 { 1346 NUMANodeAffinity: makeSocketMask(0, 1), 1347 Preferred: false, 1348 }, 1349 }, 1350 }, 1351 }, 1352 { 1353 description: "Single device type, less already allocated to container than requested", 1354 pod: &v1.Pod{ 1355 ObjectMeta: metav1.ObjectMeta{ 1356 UID: "fakePod", 1357 }, 1358 Spec: v1.PodSpec{ 1359 Containers: []v1.Container{ 1360 { 1361 Name: "fakeContainer", 1362 Resources: v1.ResourceRequirements{ 1363 Limits: v1.ResourceList{ 1364 v1.ResourceName("testdevice"): resource.MustParse("4"), 1365 }, 1366 }, 1367 }, 1368 }, 1369 }, 1370 }, 1371 devices: map[string][]pluginapi.Device{ 1372 "testdevice": { 1373 makeNUMADevice("Dev1", 0), 1374 makeNUMADevice("Dev2", 0), 1375 makeNUMADevice("Dev3", 1), 1376 makeNUMADevice("Dev4", 1), 1377 }, 1378 }, 1379 allocatedDevices: map[string]map[string]map[string][]string{ 1380 "fakePod": { 1381 "fakeContainer": { 1382 "testdevice": {"Dev1", "Dev2"}, 1383 }, 1384 }, 1385 }, 1386 expectedHints: map[string][]topologymanager.TopologyHint{ 1387 "testdevice": {}, 1388 }, 1389 }, 1390 { 1391 description: "Single device type, more already allocated to container than requested", 1392 pod: &v1.Pod{ 1393 ObjectMeta: metav1.ObjectMeta{ 1394 UID: "fakePod", 1395 }, 1396 Spec: v1.PodSpec{ 1397 Containers: []v1.Container{ 1398 { 1399 Name: "fakeContainer", 1400 Resources: v1.ResourceRequirements{ 1401 Limits: v1.ResourceList{ 1402 v1.ResourceName("testdevice"): resource.MustParse("2"), 1403 }, 1404 }, 1405 }, 1406 }, 1407 }, 1408 }, 1409 devices: map[string][]pluginapi.Device{ 1410 "testdevice": { 1411 makeNUMADevice("Dev1", 0), 1412 makeNUMADevice("Dev2", 0), 1413 makeNUMADevice("Dev3", 1), 1414 makeNUMADevice("Dev4", 1), 1415 }, 1416 }, 1417 allocatedDevices: map[string]map[string]map[string][]string{ 1418 "fakePod": { 1419 "fakeContainer": { 1420 "testdevice": {"Dev1", "Dev2", "Dev3", "Dev4"}, 1421 }, 1422 }, 1423 }, 1424 expectedHints: map[string][]topologymanager.TopologyHint{ 1425 "testdevice": {}, 1426 }, 1427 }, 1428 } 1429 } 1430 1431 func getPodScopeTestCases() []topologyHintTestCase { 1432 return []topologyHintTestCase{ 1433 { 1434 description: "2 device types, user container only", 1435 pod: &v1.Pod{ 1436 ObjectMeta: metav1.ObjectMeta{ 1437 UID: "fakePod", 1438 }, 1439 Spec: v1.PodSpec{ 1440 Containers: []v1.Container{ 1441 { 1442 Name: "fakeContainer1", 1443 Resources: v1.ResourceRequirements{ 1444 Limits: v1.ResourceList{ 1445 v1.ResourceName("testdevice1"): resource.MustParse("2"), 1446 }, 1447 }, 1448 }, 1449 { 1450 Name: "fakeContainer2", 1451 Resources: v1.ResourceRequirements{ 1452 Limits: v1.ResourceList{ 1453 v1.ResourceName("testdevice2"): resource.MustParse("2"), 1454 }, 1455 }, 1456 }, 1457 { 1458 Name: "fakeContainer3", 1459 Resources: v1.ResourceRequirements{ 1460 Limits: v1.ResourceList{ 1461 v1.ResourceName("notRegistered"): resource.MustParse("2"), 1462 }, 1463 }, 1464 }, 1465 }, 1466 }, 1467 }, 1468 devices: map[string][]pluginapi.Device{ 1469 "testdevice1": { 1470 makeNUMADevice("Dev1", 0), 1471 makeNUMADevice("Dev2", 0), 1472 makeNUMADevice("Dev3", 1), 1473 makeNUMADevice("Dev4", 1), 1474 }, 1475 "testdevice2": { 1476 makeNUMADevice("Dev1", 0), 1477 makeNUMADevice("Dev2", 0), 1478 makeNUMADevice("Dev3", 1), 1479 makeNUMADevice("Dev4", 1), 1480 }, 1481 }, 1482 expectedHints: map[string][]topologymanager.TopologyHint{ 1483 "testdevice1": { 1484 { 1485 NUMANodeAffinity: makeSocketMask(0), 1486 Preferred: true, 1487 }, 1488 { 1489 NUMANodeAffinity: makeSocketMask(1), 1490 Preferred: true, 1491 }, 1492 { 1493 NUMANodeAffinity: makeSocketMask(0, 1), 1494 Preferred: false, 1495 }, 1496 }, 1497 "testdevice2": { 1498 { 1499 NUMANodeAffinity: makeSocketMask(0), 1500 Preferred: true, 1501 }, 1502 { 1503 NUMANodeAffinity: makeSocketMask(1), 1504 Preferred: true, 1505 }, 1506 { 1507 NUMANodeAffinity: makeSocketMask(0, 1), 1508 Preferred: false, 1509 }, 1510 }, 1511 }, 1512 }, 1513 { 1514 description: "2 device types, request resources for init containers and user container", 1515 pod: &v1.Pod{ 1516 ObjectMeta: metav1.ObjectMeta{ 1517 UID: "fakePod", 1518 }, 1519 Spec: v1.PodSpec{ 1520 InitContainers: []v1.Container{ 1521 { 1522 Resources: v1.ResourceRequirements{ 1523 Limits: v1.ResourceList{ 1524 v1.ResourceName("testdevice1"): resource.MustParse("1"), 1525 v1.ResourceName("testdevice2"): resource.MustParse("1"), 1526 }, 1527 }, 1528 }, 1529 { 1530 Resources: v1.ResourceRequirements{ 1531 Limits: v1.ResourceList{ 1532 v1.ResourceName("testdevice1"): resource.MustParse("1"), 1533 v1.ResourceName("testdevice2"): resource.MustParse("2"), 1534 }, 1535 }, 1536 }, 1537 }, 1538 Containers: []v1.Container{ 1539 { 1540 Name: "fakeContainer1", 1541 Resources: v1.ResourceRequirements{ 1542 Limits: v1.ResourceList{ 1543 v1.ResourceName("testdevice1"): resource.MustParse("1"), 1544 v1.ResourceName("testdevice2"): resource.MustParse("1"), 1545 }, 1546 }, 1547 }, 1548 { 1549 Name: "fakeContainer2", 1550 Resources: v1.ResourceRequirements{ 1551 Limits: v1.ResourceList{ 1552 v1.ResourceName("testdevice1"): resource.MustParse("1"), 1553 v1.ResourceName("testdevice2"): resource.MustParse("1"), 1554 }, 1555 }, 1556 }, 1557 { 1558 Name: "fakeContainer3", 1559 Resources: v1.ResourceRequirements{ 1560 Limits: v1.ResourceList{ 1561 v1.ResourceName("notRegistered"): resource.MustParse("1"), 1562 }, 1563 }, 1564 }, 1565 }, 1566 }, 1567 }, 1568 devices: map[string][]pluginapi.Device{ 1569 "testdevice1": { 1570 makeNUMADevice("Dev1", 0), 1571 makeNUMADevice("Dev2", 0), 1572 makeNUMADevice("Dev3", 1), 1573 makeNUMADevice("Dev4", 1), 1574 }, 1575 "testdevice2": { 1576 makeNUMADevice("Dev1", 0), 1577 makeNUMADevice("Dev2", 0), 1578 makeNUMADevice("Dev3", 1), 1579 makeNUMADevice("Dev4", 1), 1580 }, 1581 }, 1582 expectedHints: map[string][]topologymanager.TopologyHint{ 1583 "testdevice1": { 1584 { 1585 NUMANodeAffinity: makeSocketMask(0), 1586 Preferred: true, 1587 }, 1588 { 1589 NUMANodeAffinity: makeSocketMask(1), 1590 Preferred: true, 1591 }, 1592 { 1593 NUMANodeAffinity: makeSocketMask(0, 1), 1594 Preferred: false, 1595 }, 1596 }, 1597 "testdevice2": { 1598 { 1599 NUMANodeAffinity: makeSocketMask(0), 1600 Preferred: true, 1601 }, 1602 { 1603 NUMANodeAffinity: makeSocketMask(1), 1604 Preferred: true, 1605 }, 1606 { 1607 NUMANodeAffinity: makeSocketMask(0, 1), 1608 Preferred: false, 1609 }, 1610 }, 1611 }, 1612 }, 1613 { 1614 description: "2 device types, user container only, optimal on 1 NUMA node, forced cross-NUMA", 1615 pod: &v1.Pod{ 1616 ObjectMeta: metav1.ObjectMeta{ 1617 UID: "fakePod", 1618 }, 1619 Spec: v1.PodSpec{ 1620 Containers: []v1.Container{ 1621 { 1622 Name: "fakeContainer1", 1623 Resources: v1.ResourceRequirements{ 1624 Limits: v1.ResourceList{ 1625 v1.ResourceName("testdevice1"): resource.MustParse("1"), 1626 v1.ResourceName("testdevice2"): resource.MustParse("1"), 1627 }, 1628 }, 1629 }, 1630 { 1631 Name: "fakeContainer2", 1632 Resources: v1.ResourceRequirements{ 1633 Limits: v1.ResourceList{ 1634 v1.ResourceName("testdevice1"): resource.MustParse("1"), 1635 v1.ResourceName("testdevice2"): resource.MustParse("1"), 1636 }, 1637 }, 1638 }, 1639 { 1640 Name: "fakeContainer3", 1641 Resources: v1.ResourceRequirements{ 1642 Limits: v1.ResourceList{ 1643 v1.ResourceName("notRegistered"): resource.MustParse("1"), 1644 }, 1645 }, 1646 }, 1647 }, 1648 }, 1649 }, 1650 devices: map[string][]pluginapi.Device{ 1651 "testdevice1": { 1652 makeNUMADevice("Dev1", 0), 1653 makeNUMADevice("Dev2", 0), 1654 makeNUMADevice("Dev3", 1), 1655 makeNUMADevice("Dev4", 1), 1656 }, 1657 "testdevice2": { 1658 makeNUMADevice("Dev1", 0), 1659 makeNUMADevice("Dev2", 0), 1660 makeNUMADevice("Dev3", 1), 1661 makeNUMADevice("Dev4", 1), 1662 }, 1663 }, 1664 allocatedDevices: map[string]map[string]map[string][]string{ 1665 "fakeOtherPod": { 1666 "fakeOtherContainer": { 1667 "testdevice1": {"Dev1", "Dev3"}, 1668 "testdevice2": {"Dev1", "Dev3"}, 1669 }, 1670 }, 1671 }, 1672 expectedHints: map[string][]topologymanager.TopologyHint{ 1673 "testdevice1": { 1674 { 1675 NUMANodeAffinity: makeSocketMask(0, 1), 1676 Preferred: false, 1677 }, 1678 }, 1679 "testdevice2": { 1680 { 1681 NUMANodeAffinity: makeSocketMask(0, 1), 1682 Preferred: false, 1683 }, 1684 }, 1685 }, 1686 }, 1687 } 1688 }