From e2ecfb6befc86f257bcab761d02744008e0f02ef Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 3 Dec 2025 11:50:45 +0200 Subject: [PATCH 1/2] topology-aware: allow exhausting idle shared pools. Allow slicing idle shared pools empty for exclusive allocations. Signed-off-by: Krisztian Litkey --- cmd/plugins/topology-aware/policy/pools.go | 4 ++-- cmd/plugins/topology-aware/policy/resources.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/plugins/topology-aware/policy/pools.go b/cmd/plugins/topology-aware/policy/pools.go index 4761fb1ea..e28972c1f 100644 --- a/cmd/plugins/topology-aware/policy/pools.go +++ b/cmd/plugins/topology-aware/policy/pools.go @@ -734,10 +734,10 @@ func (p *policy) compareScores(request Request, pools []Node, scores map[int]Sco // a node with insufficient isolated or shared capacity loses switch { - case cpuType == cpuNormal && ((isolated2 < 0 && isolated1 >= 0) || (shared2 <= 0 && shared1 > 0)): + case cpuType == cpuNormal && ((isolated2 < 0 && isolated1 >= 0) || (shared2 < 0 && shared1 >= 0)): log.Debug(" => %s loses, insufficent isolated or shared", node2.Name()) return true - case cpuType == cpuNormal && ((isolated1 < 0 && isolated2 >= 0) || (shared1 <= 0 && shared2 > 0)): + case cpuType == cpuNormal && ((isolated1 < 0 && isolated2 >= 0) || (shared1 < 0 && shared2 >= 0)): log.Debug(" => %s loses, insufficent isolated or shared", node1.Name()) return false case cpuType == cpuReserved && reserved2 < 0 && reserved1 >= 0: diff --git a/cmd/plugins/topology-aware/policy/resources.go b/cmd/plugins/topology-aware/policy/resources.go index 454b8e6ac..eb525d9a3 100644 --- a/cmd/plugins/topology-aware/policy/resources.go +++ b/cmd/plugins/topology-aware/policy/resources.go @@ -435,7 +435,7 @@ func (cs *supply) AllocateCPU(r Request) (Grant, error) { cs.node.Name(), full, cs.isolated, err) } - case full > 0 && cs.AllocatableSharedCPU() > 1000*full: + case full > 0 && cs.AllocatableSharedCPU() >= 1000*full: sliceable, err := cs.SliceableCPUs() if err != nil { return nil, policyError("internal error: "+ From f0b7b26575b32a99191de25e5e06895f7906cc83 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 3 Dec 2025 12:10:21 +0200 Subject: [PATCH 2/2] e2e: add test for exhausting idle shared pools. Signed-off-by: Krisztian Litkey --- .../test16-idle-shared-pools/code.var.sh | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/e2e/policies.test-suite/topology-aware/n4c16/test16-idle-shared-pools/code.var.sh diff --git a/test/e2e/policies.test-suite/topology-aware/n4c16/test16-idle-shared-pools/code.var.sh b/test/e2e/policies.test-suite/topology-aware/n4c16/test16-idle-shared-pools/code.var.sh new file mode 100644 index 000000000..d08aa36da --- /dev/null +++ b/test/e2e/policies.test-suite/topology-aware/n4c16/test16-idle-shared-pools/code.var.sh @@ -0,0 +1,47 @@ +vm-command "kubectl delete pods --all --now" +helm-terminate + +helm_config=$(COLOCATE_PODS=false instantiate helm-config.yaml) helm-launch topology-aware + +# Test that we allow slicing idle shared pools empty. +CPU=4 MEM=100M CONTCOUNT=3 create guaranteed +verify 'disjoint_sets(cpus["pod0c0"],cpus["pod0c1"],cpus["pod0c2"])' +verify 'len(cpus["pod0c0"]) == 4' \ + 'len(cpus["pod0c1"]) == 4' \ + 'len(cpus["pod0c2"]) == 4' + +verify 'disjoint_sets(nodes["pod0c0"],nodes["pod0c1"],nodes["pod0c2"])' +verify 'len(nodes["pod0c0"]) == 1' \ + 'len(nodes["pod0c1"]) == 1' \ + 'len(nodes["pod0c2"]) == 1' + +# Test that BestEffort and Burstable containers are now placed in the only +# remaining pool with free CPU. +CONTCOUNT=4 create besteffort +verify 'cpus["pod1c0"] == cpus["pod1c1"]' \ + 'cpus["pod1c0"] == cpus["pod1c2"]' \ + 'cpus["pod1c0"] == cpus["pod1c3"]' + +CONTCOUNT=4 CPUREQ=100m CPULIM=150m create burstable +verify 'cpus["pod2c0"] == cpus["pod2c1"]' \ + 'cpus["pod2c0"] == cpus["pod2c2"]' \ + 'cpus["pod2c0"] == cpus["pod2c3"]' \ + 'cpus["pod2c0"] == cpus["pod1c0"]' +verify 'disjoint_sets(nodes["pod0c0"],nodes["pod0c1"],nodes["pod0c2"],nodes["pod1c0"])' + +vm-command "kubectl delete pods --all --now" + +# Now test the other way around. First spread 2 besteffort containers +# around NUMA nodes. Then create a guaranteed one with 4 CPUs and check +# that it gets allocated to a full NUMA node. +CONTCOUNT=2 create besteffort +verify 'disjoint_sets(cpus["pod3c0"],cpus["pod3c1"])' + +CPU=4 MEM=100M CONTCOUNT=1 create guaranteed +verify 'disjoint_sets(cpus["pod4c0"],cpus["pod3c0"],cpus["pod3c1"])' +verify 'len(cpus["pod4c0"]) == 4' \ + 'len(nodes["pod4c0"]) == 1' +verify 'disjoint_sets(nodes["pod3c0"],nodes["pod3c1"],nodes["pod4c0"])' + +vm-command "kubectl delete pods --all --now" +helm-terminate