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
30 changes: 28 additions & 2 deletions pkg/kubelet/cm/devicemanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ type sourcesReadyStub struct{}
// PodReusableDevices is a map by pod name of devices to reuse.
type PodReusableDevices map[string]map[string]sets.String

type nodePriority int

const (
priorityFromAffinity nodePriority = iota
priorityNotFromAffinity
priorityWithoutTopology
)

func (s *sourcesReadyStub) AddSource(source string) {}
func (s *sourcesReadyStub) AllReady() bool { return true }

Expand Down Expand Up @@ -791,9 +799,27 @@ func (m *ManagerImpl) filterByAffinity(podUID, contName, resource string, availa
nodes = append(nodes, node)
}

// Sort the list of nodes by how many devices they contain.
getNodePriority := func(n int) nodePriority {
if hint.NUMANodeAffinity.IsSet(n) {
return priorityFromAffinity
}

if n != nodeWithoutTopology {
return priorityNotFromAffinity
}

return priorityWithoutTopology
}

// Sort the list of nodes by nodes from affinity set, nodes from none-affinity
// set, nodes without topology set and how many devices they contain.
sort.Slice(nodes, func(i, j int) bool {
return perNodeDevices[i].Len() < perNodeDevices[j].Len()
pi, pj := getNodePriority(nodes[i]), getNodePriority(nodes[j])
if pi != pj {
return pi < pj
}

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