1
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
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 }
1689
View as plain text