Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid undesirable allocation when device is associated with multiple … #101893

Merged
merged 7 commits into from
May 21, 2021
31 changes: 29 additions & 2 deletions pkg/kubelet/cm/devicemanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,9 +791,36 @@ func (m *ManagerImpl) filterByAffinity(podUID, contName, resource string, availa
nodes = append(nodes, node)
}

// Sort the list of nodes by how many devices they contain.
// Sort the list of nodes by:
// 1) Nodes contained in the 'hint's affinity set
// 2) Nodes not contained in the 'hint's affinity set
// 3) The fake NUMANode of -1 (assuming it is included in the list)
// Within each of the groups above, sort the nodes by how many devices they contain
sort.Slice(nodes, func(i, j int) bool {
return perNodeDevices[i].Len() < perNodeDevices[j].Len()
// If one or the other of nodes[i] or nodes[j] is in the 'hint's affinity set
if hint.NUMANodeAffinity.IsSet(nodes[i]) && hint.NUMANodeAffinity.IsSet(nodes[j]) {
return perNodeDevices[nodes[i]].Len() < perNodeDevices[nodes[j]].Len()
}
if hint.NUMANodeAffinity.IsSet(nodes[i]) {
return true
}
if hint.NUMANodeAffinity.IsSet(nodes[j]) {
return false
}

// If either nodes[i] or nodes[j] are in the 'hint's affinity set (but are not -1)
if nodes[i] != nodeWithoutTopology && nodes[j] != nodeWithoutTopology {
return perNodeDevices[nodes[i]].Len() < perNodeDevices[nodes[j]].Len()
}
kikimo marked this conversation as resolved.
Show resolved Hide resolved
if nodes[i] != nodeWithoutTopology {
return true
}
if nodes[j] != nodeWithoutTopology {
return false
}

// If both nodes[i] and nodes[j] are -1
return perNodeDevices[nodes[i]].Len() < perNodeDevices[nodes[j]].Len()
kikimo marked this conversation as resolved.
Show resolved Hide resolved
})

// Generate three sorted lists of devices. Devices in the first list come
Expand Down
128 changes: 128 additions & 0 deletions pkg/kubelet/cm/devicemanager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ import (
"k8s.io/kubernetes/pkg/kubelet/checkpointmanager"
"k8s.io/kubernetes/pkg/kubelet/cm/devicemanager/checkpoint"
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager"
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
"k8s.io/kubernetes/pkg/kubelet/config"
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
"k8s.io/kubernetes/pkg/kubelet/pluginmanager"
"k8s.io/kubernetes/pkg/kubelet/util/format"
schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework"
)

Expand Down Expand Up @@ -669,6 +671,132 @@ type TestResource struct {
topology bool
}

func TestFilterByAffinity(t *testing.T) {
as := require.New(t)
allDevices := ResourceDeviceInstances{
"res1": map[string]pluginapi.Device{
"dev1": {
ID: "dev1",
Topology: &pluginapi.TopologyInfo{
Nodes: []*pluginapi.NUMANode{
{
ID: 1,
},
},
},
},
"dev2": {
ID: "dev2",
Topology: &pluginapi.TopologyInfo{
Nodes: []*pluginapi.NUMANode{
{
ID: 1,
},
{
ID: 2,
},
},
},
},
"dev3": {
ID: "dev3",
Topology: &pluginapi.TopologyInfo{
Nodes: []*pluginapi.NUMANode{
{
ID: 2,
},
},
},
},
"dev4": {
ID: "dev4",
Topology: &pluginapi.TopologyInfo{
Nodes: []*pluginapi.NUMANode{
{
ID: 2,
},
},
},
},
},
}

fakeAffinity, _ := bitmask.NewBitMask(2)
fakeHint := topologymanager.TopologyHint{
NUMANodeAffinity: fakeAffinity,
Preferred: true,
}
testManager := ManagerImpl{
topologyAffinityStore: NewFakeTopologyManagerWithHint(t, &fakeHint),
allDevices: allDevices,
}

testCases := []struct {
available sets.String
fromAffinityExpected sets.String
notFromAffinityExpected sets.String
withoutTopologyExpected sets.String
}{
{
available: sets.NewString("dev1", "dev2"),
fromAffinityExpected: sets.NewString("dev2"),
notFromAffinityExpected: sets.NewString("dev1"),
withoutTopologyExpected: sets.NewString(),
},
{
available: sets.NewString("dev1", "dev2", "dev3", "dev4"),
fromAffinityExpected: sets.NewString("dev2", "dev3", "dev4"),
notFromAffinityExpected: sets.NewString("dev1"),
withoutTopologyExpected: sets.NewString(),
},
}

for _, testCase := range testCases {
fromAffinity, notFromAffinity, withoutTopology := testManager.filterByAffinity("", "", "res1", testCase.available)
kikimo marked this conversation as resolved.
Show resolved Hide resolved
as.Truef(fromAffinity.Equal(testCase.fromAffinityExpected), "expect devices from affinity to be %v but got %v", testCase.fromAffinityExpected, fromAffinity)
as.Truef(notFromAffinity.Equal(testCase.notFromAffinityExpected), "expect devices not from affinity to be %v but got %v", testCase.notFromAffinityExpected, notFromAffinity)
as.Truef(withoutTopology.Equal(testCase.withoutTopologyExpected), "expect devices without topology to be %v but got %v", testCase.notFromAffinityExpected, notFromAffinity)
}
}

type fakeTopologyManagerWithHint struct {
t *testing.T
hint *topologymanager.TopologyHint
}

// NewFakeTopologyManagerWithHint returns an instance of fake topology manager with specified topology hints
func NewFakeTopologyManagerWithHint(t *testing.T, hint *topologymanager.TopologyHint) topologymanager.Manager {
return &fakeTopologyManagerWithHint{
t: t,
kikimo marked this conversation as resolved.
Show resolved Hide resolved
hint: hint,
}
}

func (m *fakeTopologyManagerWithHint) AddHintProvider(h topologymanager.HintProvider) {
m.t.Logf("[fake topologymanager] AddHintProvider HintProvider: %v", h)
}

func (m *fakeTopologyManagerWithHint) AddContainer(pod *v1.Pod, container *v1.Container, containerID string) {
m.t.Logf("[fake topologymanager] AddContainer pod: %v container name: %v container id: %v", format.Pod(pod), container.Name, containerID)
}

func (m *fakeTopologyManagerWithHint) RemoveContainer(containerID string) error {
m.t.Logf("[fake topologymanager] RemoveContainer container id: %v", containerID)
return nil
}

func (m *fakeTopologyManagerWithHint) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
m.t.Logf("[fake topologymanager] Topology Admit Handler")
return lifecycle.PodAdmitResult{
Admit: true,
}
}

func (m *fakeTopologyManagerWithHint) GetAffinity(podUID string, containerName string) topologymanager.TopologyHint {
m.t.Logf("[fake topologymanager] GetAffinity podUID: %v container name: %v", podUID, containerName)
return *m.hint
}

func TestPodContainerDeviceAllocation(t *testing.T) {
res1 := TestResource{
resourceName: "domain1.com/resource1",
Expand Down