1689 lines
47 KiB
Go
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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|