kubernetes/pkg/kubelet/cm/devicemanager/topology_hints_test.go
Mikko Ylinen 68bb0935bd devicemanager: do not leak empty TopologyInfo to TopologyManager
Device Plugins that wish to leverage the Topology Manager can send back a populated
TopologyInfo struct as part of the device registration, along with the device IDs
and the health of the device. TopologyInfo is converted to TopologyHints and
used by TopologyManager to find the optimal/desired resource allocation for a Pod.

If a plugin sends an empty but non-nil instance of TopologyInfo for a resource,
devicemanager passes it on as an empty instance of TopologyHint which is
currently interpreted as "Hint Provider has no possible NUMA affinities
for resource" which further means that pods requesting that resource will fail.

To not block device resources that pass TopologyInfo{Nodes:[]*NUMANode{}} from being
used, interprete that as nil set of hints and not a []TopologyHint{}.

Signed-off-by: Mikko Ylinen <mikko.ylinen@intel.com>
2022-09-14 16:13:31 +03:00

1689 lines
47 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package devicemanager
import (
"fmt"
"reflect"
"sort"
"testing"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager"
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
)
type mockAffinityStore struct {
hint topologymanager.TopologyHint
}
func (m *mockAffinityStore) GetAffinity(podUID string, containerName string) topologymanager.TopologyHint {
return m.hint
}
func (m *mockAffinityStore) GetPolicy() topologymanager.Policy {
return nil
}
func makeNUMADevice(id string, numa int) pluginapi.Device {
return pluginapi.Device{
ID: id,
Topology: &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{{ID: int64(numa)}}},
}
}
func makeSocketMask(sockets ...int) bitmask.BitMask {
mask, _ := bitmask.NewBitMask(sockets...)
return mask
}
func TestGetTopologyHints(t *testing.T) {
tcases := getCommonTestCases()
for _, tc := range tcases {
m := ManagerImpl{
allDevices: NewResourceDeviceInstances(),
healthyDevices: make(map[string]sets.String),
allocatedDevices: make(map[string]sets.String),
podDevices: newPodDevices(),
sourcesReady: &sourcesReadyStub{},
activePods: func() []*v1.Pod { return []*v1.Pod{tc.pod} },
numaNodes: []int{0, 1},
}
for r := range tc.devices {
m.allDevices[r] = make(DeviceInstances)
m.healthyDevices[r] = sets.NewString()
for _, d := range tc.devices[r] {
m.allDevices[r][d.ID] = d
m.healthyDevices[r].Insert(d.ID)
}
}
for p := range tc.allocatedDevices {
for c := range tc.allocatedDevices[p] {
for r, devices := range tc.allocatedDevices[p][c] {
m.podDevices.insert(p, c, r, constructDevices(devices), nil)
m.allocatedDevices[r] = sets.NewString()
for _, d := range devices {
m.allocatedDevices[r].Insert(d)
}
}
}
}
hints := m.GetTopologyHints(tc.pod, &tc.pod.Spec.Containers[0])
for r := range tc.expectedHints {
sort.SliceStable(hints[r], func(i, j int) bool {
return hints[r][i].LessThan(hints[r][j])
})
sort.SliceStable(tc.expectedHints[r], func(i, j int) bool {
return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j])
})
if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) {
t.Errorf("%v: Expected result to be %#v, got %#v", tc.description, tc.expectedHints[r], hints[r])
}
}
}
}
func TestTopologyAlignedAllocation(t *testing.T) {
tcases := []struct {
description string
resource string
request int
devices []pluginapi.Device
allocatedDevices []string
hint topologymanager.TopologyHint
getPreferredAllocationFunc func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error)
expectedPreferredAllocation []string
expectedAlignment map[int]int
}{
{
description: "Single Request, no alignment",
resource: "resource",
request: 1,
devices: []pluginapi.Device{
{ID: "Dev1"},
{ID: "Dev2"},
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: true,
},
expectedAlignment: map[int]int{},
},
{
description: "Request for 1, partial alignment",
resource: "resource",
request: 1,
devices: []pluginapi.Device{
{ID: "Dev1"},
makeNUMADevice("Dev2", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
expectedAlignment: map[int]int{1: 1},
},
{
description: "Single Request, socket 0",
resource: "resource",
request: 1,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAlignment: map[int]int{0: 1},
},
{
description: "Single Request, socket 1",
resource: "resource",
request: 1,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
expectedAlignment: map[int]int{1: 1},
},
{
description: "Request for 2, socket 0",
resource: "resource",
request: 2,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAlignment: map[int]int{0: 2},
},
{
description: "Request for 2, socket 1",
resource: "resource",
request: 2,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
expectedAlignment: map[int]int{1: 2},
},
{
description: "Request for 4, unsatisfiable, prefer socket 0",
resource: "resource",
request: 4,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
makeNUMADevice("Dev5", 0),
makeNUMADevice("Dev6", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAlignment: map[int]int{0: 3, 1: 1},
},
{
description: "Request for 4, unsatisfiable, prefer socket 1",
resource: "resource",
request: 4,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
makeNUMADevice("Dev5", 0),
makeNUMADevice("Dev6", 1),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
expectedAlignment: map[int]int{0: 1, 1: 3},
},
{
description: "Request for 4, multisocket",
resource: "resource",
request: 4,
devices: []pluginapi.Device{
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 2),
makeNUMADevice("Dev4", 3),
makeNUMADevice("Dev5", 0),
makeNUMADevice("Dev6", 1),
makeNUMADevice("Dev7", 2),
makeNUMADevice("Dev8", 3),
},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(1, 3),
Preferred: true,
},
expectedAlignment: map[int]int{1: 2, 3: 2},
},
{
description: "Request for 5, socket 0, preferred aligned accepted",
resource: "resource",
request: 5,
devices: func() []pluginapi.Device {
devices := []pluginapi.Device{}
for i := 0; i < 100; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 0))
}
for i := 100; i < 200; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 1))
}
return devices
}(),
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
return &pluginapi.PreferredAllocationResponse{
ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
{DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "Dev42", "Dev77"}},
},
}, nil
},
expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83", "Dev42", "Dev77"},
expectedAlignment: map[int]int{0: 5},
},
{
description: "Request for 5, socket 0, preferred aligned accepted, unaligned ignored",
resource: "resource",
request: 5,
devices: func() []pluginapi.Device {
devices := []pluginapi.Device{}
for i := 0; i < 100; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 0))
}
for i := 100; i < 200; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 1))
}
return devices
}(),
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
return &pluginapi.PreferredAllocationResponse{
ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
{DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "Dev150", "Dev186"}},
},
}, nil
},
expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83"},
expectedAlignment: map[int]int{0: 5},
},
{
description: "Request for 5, socket 1, preferred aligned accepted, bogus ignored",
resource: "resource",
request: 5,
devices: func() []pluginapi.Device {
devices := []pluginapi.Device{}
for i := 0; i < 100; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 1))
}
return devices
}(),
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
return &pluginapi.PreferredAllocationResponse{
ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
{DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "bogus0", "bogus1"}},
},
}, nil
},
expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83"},
expectedAlignment: map[int]int{1: 5},
},
{
description: "Request for 5, multisocket, preferred accepted",
resource: "resource",
request: 5,
devices: func() []pluginapi.Device {
devices := []pluginapi.Device{}
for i := 0; i < 3; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 0))
}
for i := 3; i < 100; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 1))
}
return devices
}(),
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
return &pluginapi.PreferredAllocationResponse{
ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
{DeviceIDs: []string{"Dev0", "Dev1", "Dev2", "Dev42", "Dev83"}},
},
}, nil
},
expectedPreferredAllocation: []string{"Dev0", "Dev1", "Dev2", "Dev42", "Dev83"},
expectedAlignment: map[int]int{0: 3, 1: 2},
},
{
description: "Request for 5, multisocket, preferred unaligned accepted, bogus ignored",
resource: "resource",
request: 5,
devices: func() []pluginapi.Device {
devices := []pluginapi.Device{}
for i := 0; i < 3; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 0))
}
for i := 3; i < 100; i++ {
id := fmt.Sprintf("Dev%d", i)
devices = append(devices, makeNUMADevice(id, 1))
}
return devices
}(),
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
return &pluginapi.PreferredAllocationResponse{
ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
{DeviceIDs: []string{"Dev0", "Dev1", "Dev2", "Dev42", "bogus0"}},
},
}, nil
},
expectedPreferredAllocation: []string{"Dev0", "Dev1", "Dev2", "Dev42"},
expectedAlignment: map[int]int{0: 3, 1: 2},
},
}
for _, tc := range tcases {
m := ManagerImpl{
allDevices: NewResourceDeviceInstances(),
healthyDevices: make(map[string]sets.String),
allocatedDevices: make(map[string]sets.String),
endpoints: make(map[string]endpointInfo),
podDevices: newPodDevices(),
sourcesReady: &sourcesReadyStub{},
activePods: func() []*v1.Pod { return []*v1.Pod{} },
topologyAffinityStore: &mockAffinityStore{tc.hint},
}
m.allDevices[tc.resource] = make(DeviceInstances)
m.healthyDevices[tc.resource] = sets.NewString()
m.endpoints[tc.resource] = endpointInfo{}
for _, d := range tc.devices {
m.allDevices[tc.resource][d.ID] = d
m.healthyDevices[tc.resource].Insert(d.ID)
}
if tc.getPreferredAllocationFunc != nil {
m.endpoints[tc.resource] = endpointInfo{
e: &MockEndpoint{
getPreferredAllocationFunc: tc.getPreferredAllocationFunc,
},
opts: &pluginapi.DevicePluginOptions{GetPreferredAllocationAvailable: true},
}
}
allocated, err := m.devicesToAllocate("podUID", "containerName", tc.resource, tc.request, sets.NewString())
if err != nil {
t.Errorf("Unexpected error: %v", err)
continue
}
if len(allocated) != tc.request {
t.Errorf("%v. expected allocation size: %v but got: %v", tc.description, tc.request, len(allocated))
}
if !allocated.HasAll(tc.expectedPreferredAllocation...) {
t.Errorf("%v. expected preferred allocation: %v but not present in: %v", tc.description, tc.expectedPreferredAllocation, allocated.UnsortedList())
}
alignment := make(map[int]int)
if m.deviceHasTopologyAlignment(tc.resource) {
for d := range allocated {
if m.allDevices[tc.resource][d].Topology != nil {
alignment[int(m.allDevices[tc.resource][d].Topology.Nodes[0].ID)]++
}
}
}
if !reflect.DeepEqual(alignment, tc.expectedAlignment) {
t.Errorf("%v. expected alignment: %v but got: %v", tc.description, tc.expectedAlignment, alignment)
}
}
}
func TestGetPreferredAllocationParameters(t *testing.T) {
tcases := []struct {
description string
resource string
request int
allDevices []pluginapi.Device
allocatedDevices []string
reusableDevices []string
hint topologymanager.TopologyHint
expectedAvailable []string
expectedMustInclude []string
expectedSize int
}{
{
description: "Request for 1, socket 0, 0 already allocated, 0 reusable",
resource: "resource",
request: 1,
allDevices: []pluginapi.Device{
makeNUMADevice("Dev0", 0),
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 0),
},
allocatedDevices: []string{},
reusableDevices: []string{},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAvailable: []string{"Dev0", "Dev1", "Dev2", "Dev3"},
expectedMustInclude: []string{},
expectedSize: 1,
},
{
description: "Request for 4, socket 0, 2 already allocated, 2 reusable",
resource: "resource",
request: 4,
allDevices: []pluginapi.Device{
makeNUMADevice("Dev0", 0),
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 0),
makeNUMADevice("Dev5", 0),
makeNUMADevice("Dev6", 0),
makeNUMADevice("Dev7", 0),
},
allocatedDevices: []string{"Dev0", "Dev5"},
reusableDevices: []string{"Dev0", "Dev5"},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAvailable: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6", "Dev7"},
expectedMustInclude: []string{"Dev0", "Dev5"},
expectedSize: 4,
},
{
description: "Request for 4, socket 0, 4 already allocated, 2 reusable",
resource: "resource",
request: 4,
allDevices: []pluginapi.Device{
makeNUMADevice("Dev0", 0),
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 0),
makeNUMADevice("Dev5", 0),
makeNUMADevice("Dev6", 0),
makeNUMADevice("Dev7", 0),
},
allocatedDevices: []string{"Dev0", "Dev5", "Dev4", "Dev1"},
reusableDevices: []string{"Dev0", "Dev5"},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAvailable: []string{"Dev0", "Dev2", "Dev3", "Dev5", "Dev6", "Dev7"},
expectedMustInclude: []string{"Dev0", "Dev5"},
expectedSize: 4,
},
{
description: "Request for 6, multisocket, 2 already allocated, 2 reusable",
resource: "resource",
request: 6,
allDevices: []pluginapi.Device{
makeNUMADevice("Dev0", 0),
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
makeNUMADevice("Dev5", 1),
makeNUMADevice("Dev6", 1),
makeNUMADevice("Dev7", 1),
},
allocatedDevices: []string{"Dev1", "Dev6"},
reusableDevices: []string{"Dev1", "Dev6"},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAvailable: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6", "Dev7"},
expectedMustInclude: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev6"},
expectedSize: 6,
},
{
description: "Request for 6, multisocket, 4 already allocated, 2 reusable",
resource: "resource",
request: 6,
allDevices: []pluginapi.Device{
makeNUMADevice("Dev0", 0),
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
makeNUMADevice("Dev5", 1),
makeNUMADevice("Dev6", 1),
makeNUMADevice("Dev7", 1),
},
allocatedDevices: []string{"Dev0", "Dev1", "Dev6", "Dev7"},
reusableDevices: []string{"Dev1", "Dev6"},
hint: topologymanager.TopologyHint{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
expectedAvailable: []string{"Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6"},
expectedMustInclude: []string{"Dev1", "Dev2", "Dev3", "Dev6"},
expectedSize: 6,
},
}
for _, tc := range tcases {
m := ManagerImpl{
allDevices: NewResourceDeviceInstances(),
healthyDevices: make(map[string]sets.String),
allocatedDevices: make(map[string]sets.String),
endpoints: make(map[string]endpointInfo),
podDevices: newPodDevices(),
sourcesReady: &sourcesReadyStub{},
activePods: func() []*v1.Pod { return []*v1.Pod{} },
topologyAffinityStore: &mockAffinityStore{tc.hint},
}
m.allDevices[tc.resource] = make(DeviceInstances)
m.healthyDevices[tc.resource] = sets.NewString()
for _, d := range tc.allDevices {
m.allDevices[tc.resource][d.ID] = d
m.healthyDevices[tc.resource].Insert(d.ID)
}
m.allocatedDevices[tc.resource] = sets.NewString()
for _, d := range tc.allocatedDevices {
m.allocatedDevices[tc.resource].Insert(d)
}
actualAvailable := []string{}
actualMustInclude := []string{}
actualSize := 0
m.endpoints[tc.resource] = endpointInfo{
e: &MockEndpoint{
getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
actualAvailable = append(actualAvailable, available...)
actualMustInclude = append(actualMustInclude, mustInclude...)
actualSize = size
return nil, nil
},
},
opts: &pluginapi.DevicePluginOptions{GetPreferredAllocationAvailable: true},
}
_, err := m.devicesToAllocate("podUID", "containerName", tc.resource, tc.request, sets.NewString(tc.reusableDevices...))
if err != nil {
t.Errorf("Unexpected error: %v", err)
continue
}
if !sets.NewString(actualAvailable...).Equal(sets.NewString(tc.expectedAvailable...)) {
t.Errorf("%v. expected available: %v but got: %v", tc.description, tc.expectedAvailable, actualAvailable)
}
if !sets.NewString(actualAvailable...).Equal(sets.NewString(tc.expectedAvailable...)) {
t.Errorf("%v. expected mustInclude: %v but got: %v", tc.description, tc.expectedMustInclude, actualMustInclude)
}
if actualSize != tc.expectedSize {
t.Errorf("%v. expected size: %v but got: %v", tc.description, tc.expectedSize, actualSize)
}
}
}
func TestGetPodDeviceRequest(t *testing.T) {
tcases := []struct {
description string
pod *v1.Pod
registeredDevices []string
expected map[string]int
}{
{
description: "empty pod",
pod: &v1.Pod{},
registeredDevices: []string{},
expected: map[string]int{},
},
{
description: "Init container requests device plugin resource",
pod: &v1.Pod{
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
},
},
},
},
},
},
registeredDevices: []string{"gpu"},
expected: map[string]int{"gpu": 2},
},
{
description: "Init containers request device plugin resource",
pod: &v1.Pod{
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("4"),
},
},
},
},
},
},
registeredDevices: []string{"gpu"},
expected: map[string]int{"gpu": 4},
},
{
description: "User container requests device plugin resource",
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
},
},
},
},
},
},
registeredDevices: []string{"gpu"},
expected: map[string]int{"gpu": 2},
},
{
description: "Init containers and user containers request the same amount of device plugin resources",
pod: &v1.Pod{
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
v1.ResourceName("nic"): resource.MustParse("2"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
v1.ResourceName("nic"): resource.MustParse("2"),
},
},
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("1"),
v1.ResourceName("nic"): resource.MustParse("1"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("1"),
v1.ResourceName("nic"): resource.MustParse("1"),
},
},
},
},
},
},
registeredDevices: []string{"gpu", "nic"},
expected: map[string]int{"gpu": 2, "nic": 2},
},
{
description: "Init containers request more device plugin resources than user containers",
pod: &v1.Pod{
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
v1.ResourceName("nic"): resource.MustParse("1"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("3"),
v1.ResourceName("nic"): resource.MustParse("2"),
},
},
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("1"),
v1.ResourceName("nic"): resource.MustParse("1"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("1"),
},
},
},
},
},
},
registeredDevices: []string{"gpu", "nic"},
expected: map[string]int{"gpu": 3, "nic": 2},
},
{
description: "User containers request more device plugin resources than init containers",
pod: &v1.Pod{
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
v1.ResourceName("nic"): resource.MustParse("1"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("2"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("2"),
v1.ResourceName("nic"): resource.MustParse("1"),
},
},
},
},
Containers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("3"),
v1.ResourceName("nic"): resource.MustParse("2"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName(v1.ResourceCPU): resource.MustParse("1"),
v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
v1.ResourceName("gpu"): resource.MustParse("3"),
v1.ResourceName("nic"): resource.MustParse("2"),
},
},
},
},
},
},
registeredDevices: []string{"gpu", "nic"},
expected: map[string]int{"gpu": 6, "nic": 4},
},
}
for _, tc := range tcases {
m := ManagerImpl{
healthyDevices: make(map[string]sets.String),
}
for _, res := range tc.registeredDevices {
m.healthyDevices[res] = sets.NewString()
}
accumulatedResourceRequests := m.getPodDeviceRequest(tc.pod)
if !reflect.DeepEqual(accumulatedResourceRequests, tc.expected) {
t.Errorf("%v. expected alignment: %v but got: %v", tc.description, tc.expected, accumulatedResourceRequests)
}
}
}
func TestGetPodTopologyHints(t *testing.T) {
tcases := getCommonTestCases()
tcases = append(tcases, getPodScopeTestCases()...)
for _, tc := range tcases {
m := ManagerImpl{
allDevices: NewResourceDeviceInstances(),
healthyDevices: make(map[string]sets.String),
allocatedDevices: make(map[string]sets.String),
podDevices: newPodDevices(),
sourcesReady: &sourcesReadyStub{},
activePods: func() []*v1.Pod { return []*v1.Pod{tc.pod, {ObjectMeta: metav1.ObjectMeta{UID: "fakeOtherPod"}}} },
numaNodes: []int{0, 1},
}
for r := range tc.devices {
m.allDevices[r] = make(DeviceInstances)
m.healthyDevices[r] = sets.NewString()
for _, d := range tc.devices[r] {
//add `pluginapi.Device` with Topology
m.allDevices[r][d.ID] = d
m.healthyDevices[r].Insert(d.ID)
}
}
for p := range tc.allocatedDevices {
for c := range tc.allocatedDevices[p] {
for r, devices := range tc.allocatedDevices[p][c] {
m.podDevices.insert(p, c, r, constructDevices(devices), nil)
m.allocatedDevices[r] = sets.NewString()
for _, d := range devices {
m.allocatedDevices[r].Insert(d)
}
}
}
}
hints := m.GetPodTopologyHints(tc.pod)
for r := range tc.expectedHints {
sort.SliceStable(hints[r], func(i, j int) bool {
return hints[r][i].LessThan(hints[r][j])
})
sort.SliceStable(tc.expectedHints[r], func(i, j int) bool {
return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j])
})
if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) {
t.Errorf("%v: Expected result to be %v, got %v", tc.description, tc.expectedHints[r], hints[r])
}
}
}
}
type topologyHintTestCase struct {
description string
pod *v1.Pod
devices map[string][]pluginapi.Device
allocatedDevices map[string]map[string]map[string][]string
expectedHints map[string][]topologymanager.TopologyHint
}
func getCommonTestCases() []topologyHintTestCase {
return []topologyHintTestCase{
{
description: "Single Request, no alignment",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("1"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
{ID: "Dev1"},
{ID: "Dev2"},
{ID: "Dev3", Topology: &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{}}},
{ID: "Dev4", Topology: &pluginapi.TopologyInfo{Nodes: nil}},
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": nil,
},
},
{
description: "Single Request, only one with alignment",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("1"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
{ID: "Dev1"},
makeNUMADevice("Dev2", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "Single Request, one device per socket",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("1"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "Request for 2, one device per socket",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("2"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: true,
},
},
},
},
{
description: "Request for 2, 2 devices per socket",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("2"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "Request for 2, optimal on 1 NUMA node, forced cross-NUMA",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("2"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
},
},
allocatedDevices: map[string]map[string]map[string][]string{
"fakePod": {
"fakeOtherContainer": {
"testdevice": {"Dev1", "Dev2"},
},
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "2 device types, mixed configuration",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("2"),
v1.ResourceName("testdevice2"): resource.MustParse("1"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice1": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 1),
makeNUMADevice("Dev3", 0),
makeNUMADevice("Dev4", 1),
},
"testdevice2": {
makeNUMADevice("Dev1", 0),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice1": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
"testdevice2": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "Single device type, more requested than available",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("6"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {},
},
},
{
description: "Single device type, all already allocated to container",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("2"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
},
},
allocatedDevices: map[string]map[string]map[string][]string{
"fakePod": {
"fakeContainer": {
"testdevice": {"Dev1", "Dev2"},
},
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "Single device type, less already allocated to container than requested",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("4"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
},
allocatedDevices: map[string]map[string]map[string][]string{
"fakePod": {
"fakeContainer": {
"testdevice": {"Dev1", "Dev2"},
},
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {},
},
},
{
description: "Single device type, more already allocated to container than requested",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice"): resource.MustParse("2"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
},
allocatedDevices: map[string]map[string]map[string][]string{
"fakePod": {
"fakeContainer": {
"testdevice": {"Dev1", "Dev2", "Dev3", "Dev4"},
},
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice": {},
},
},
}
}
func getPodScopeTestCases() []topologyHintTestCase {
return []topologyHintTestCase{
{
description: "2 device types, user container only",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer1",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("2"),
},
},
},
{
Name: "fakeContainer2",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice2"): resource.MustParse("2"),
},
},
},
{
Name: "fakeContainer3",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("notRegistered"): resource.MustParse("2"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice1": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
"testdevice2": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice1": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
"testdevice2": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "2 device types, request resources for init containers and user container",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("1"),
v1.ResourceName("testdevice2"): resource.MustParse("1"),
},
},
},
{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("1"),
v1.ResourceName("testdevice2"): resource.MustParse("2"),
},
},
},
},
Containers: []v1.Container{
{
Name: "fakeContainer1",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("1"),
v1.ResourceName("testdevice2"): resource.MustParse("1"),
},
},
},
{
Name: "fakeContainer2",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("1"),
v1.ResourceName("testdevice2"): resource.MustParse("1"),
},
},
},
{
Name: "fakeContainer3",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("notRegistered"): resource.MustParse("1"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice1": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
"testdevice2": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice1": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
"testdevice2": {
{
NUMANodeAffinity: makeSocketMask(0),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(1),
Preferred: true,
},
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
{
description: "2 device types, user container only, optimal on 1 NUMA node, forced cross-NUMA",
pod: &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "fakePod",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fakeContainer1",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("1"),
v1.ResourceName("testdevice2"): resource.MustParse("1"),
},
},
},
{
Name: "fakeContainer2",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("testdevice1"): resource.MustParse("1"),
v1.ResourceName("testdevice2"): resource.MustParse("1"),
},
},
},
{
Name: "fakeContainer3",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
v1.ResourceName("notRegistered"): resource.MustParse("1"),
},
},
},
},
},
},
devices: map[string][]pluginapi.Device{
"testdevice1": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
"testdevice2": {
makeNUMADevice("Dev1", 0),
makeNUMADevice("Dev2", 0),
makeNUMADevice("Dev3", 1),
makeNUMADevice("Dev4", 1),
},
},
allocatedDevices: map[string]map[string]map[string][]string{
"fakeOtherPod": {
"fakeOtherContainer": {
"testdevice1": {"Dev1", "Dev3"},
"testdevice2": {"Dev1", "Dev3"},
},
},
},
expectedHints: map[string][]topologymanager.TopologyHint{
"testdevice1": {
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
"testdevice2": {
{
NUMANodeAffinity: makeSocketMask(0, 1),
Preferred: false,
},
},
},
},
}
}