This is an automated email from the ASF dual-hosted git repository.

manirajv06 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 77146ee3 [YUNIKORN-3137] Fix preemption victim selection vector 
comparison flaw for larger asks (#1148)
77146ee3 is described below

commit 77146ee33cdf5373d1ba2120fe7484843ebf9b72
Author: hedger9487 <[email protected]>
AuthorDate: Thu Sep 10 09:55:46 2026 +0530

    [YUNIKORN-3137] Fix preemption victim selection vector comparison flaw for 
larger asks (#1148)
    
    Closes: #1148
    
    Signed-off-by: mani <[email protected]>
---
 pkg/scheduler/objects/preemption.go      |  31 ++++--
 pkg/scheduler/objects/preemption_test.go | 172 +++++++++++++++++--------------
 2 files changed, 115 insertions(+), 88 deletions(-)

diff --git a/pkg/scheduler/objects/preemption.go 
b/pkg/scheduler/objects/preemption.go
index 0ca782c5..0ec90804 100644
--- a/pkg/scheduler/objects/preemption.go
+++ b/pkg/scheduler/objects/preemption.go
@@ -636,11 +636,7 @@ func (p *Preemptor) TryPreemption() (*AllocationResult, 
bool) {
        // Holds total victims resources
        victimsTotalResource := resources.NewResource()
 
-       fitIn := false
-       nodeCurrentAvailable := p.nodeAvailableMap
-       if nodeCurrentAvailable[nodeID].FitIn(p.ask.GetAllocatedResource()) {
-               fitIn = true
-       }
+       fitIn := p.nodeAvailableMap[nodeID].FitIn(p.ask.GetAllocatedResource())
 
        // Since there could be more victims than the actual need, ensure only 
required victims are filtered finally
        // to do: There is room for improvements especially when there are more 
victims. victims could be chosen based
@@ -653,15 +649,28 @@ func (p *Preemptor) TryPreemption() (*AllocationResult, 
bool) {
                if !fitIn && victim.GetNodeID() != nodeID {
                        continue
                }
-               // stop collecting the victims once ask resource requirement met
-               if 
p.ask.GetAllocatedResource().StrictlyGreaterThanOnlyExisting(victimsTotalResource)
 {
-                       finalVictims = append(finalVictims, victim)
+               // check if victim contributes to any resource dimension that 
is still needed
+               allocRes := victim.GetAllocatedResource()
+               for k, needVal := range p.ask.GetAllocatedResource().Resources {
+                       if victimsTotalResource.Resources[k] < needVal && 
allocRes.Resources[k] > 0 {
+                               finalVictims = append(finalVictims, victim)
+                               victimsTotalResource.AddTo(allocRes)
+                               break
+                       }
+               }
+       }
+
+       hasShortfall := victimsTotalResource.IsEmpty()
+       if !hasShortfall {
+               for k, victimVal := range victimsTotalResource.Resources {
+                       if needVal, ok := 
p.ask.GetAllocatedResource().Resources[k]; ok && victimVal < needVal {
+                               hasShortfall = true
+                               break
+                       }
                }
-               // add the victim resources to the total
-               victimsTotalResource.AddTo(victim.GetAllocatedResource())
        }
 
-       if 
p.ask.GetAllocatedResource().StrictlyGreaterThanOnlyExisting(victimsTotalResource)
 {
+       if hasShortfall {
                // there is shortfall, so preemption doesn't help
                p.ask.LogAllocationFailure(common.PreemptionShortfall, true)
                return nil, false
diff --git a/pkg/scheduler/objects/preemption_test.go 
b/pkg/scheduler/objects/preemption_test.go
index a6f586b8..89af4320 100644
--- a/pkg/scheduler/objects/preemption_test.go
+++ b/pkg/scheduler/objects/preemption_test.go
@@ -721,7 +721,7 @@ func 
TestTryPreemption_VictimsAvailable_InsufficientResource(t *testing.T) {
        alloc1, alloc2, err := creatApp1(childQ1, node1, node2, 
map[string]resources.Quantity{"first": 2, "pods": 1}, appQueueMapping)
        assert.NilError(t, err)
 
-       app2, ask3, err := creatApp2(childQ2, 
map[string]resources.Quantity{"first": 5}, "alloc3", appQueueMapping)
+       app2, ask3, err := creatApp2(childQ2, 
map[string]resources.Quantity{"first": 5, "pods": 1}, "alloc3", appQueueMapping)
        assert.NilError(t, err)
 
        headRoom := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "pods": 
3})
@@ -1459,88 +1459,106 @@ func createVictimApplications(childQ2 *Queue, 
appQueueMapping *AppQueueMapping)
 //
 //nolint:funlen
 func TestTryPreemption_AskResTypesSame_GuaranteedSetOnPreemptorSide(t 
*testing.T) {
-       appQueueMapping := NewAppQueueMapping()
-       node := newNode(nodeID1, map[string]resources.Quantity{"vcores": 5, 
"gpu": 300, "mem": 200})
-       iterator := getNodeIteratorFn(node)
-       rootQ, err := createRootQueue(map[string]string{"vcores": "5", "gpu": 
"300", "mem": "200"})
-       assert.NilError(t, err)
-       parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true, 
nil, nil, appQueueMapping)
-       assert.NilError(t, err)
-       parentQ1, err := createManagedQueueGuaranteed(parentQ, "parent1", true, 
nil, nil, appQueueMapping)
-       assert.NilError(t, err)
-       parentQ2, err := createManagedQueueGuaranteed(parentQ, "parent2", true, 
nil, nil, appQueueMapping)
-       assert.NilError(t, err)
-
-       childQ1, err := createManagedQueueGuaranteed(parentQ1, "child1", false, 
nil, map[string]string{"vcores": "2"}, appQueueMapping)
-       assert.NilError(t, err)
-       childQ2, err := createManagedQueueGuaranteed(parentQ2, "child2", false, 
nil, nil, appQueueMapping)
-       assert.NilError(t, err)
-       _, err = createManagedQueueGuaranteed(parentQ2, "child3", false, nil, 
nil, appQueueMapping)
-       assert.NilError(t, err)
-       app1, app2, app3 := createVictimApplications(childQ2, appQueueMapping)
-       for i := 5; i < 8; i++ {
-               askN := newAllocationAsk(alloc+strconv.Itoa(i), appID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"gpu": 100}))
-               askN.createTime = time.Now().Add(-2 * time.Minute)
-               assert.NilError(t, app1.AddAllocationAsk(askN))
-               allocN := newAllocationWithKey(askN.allocationKey, appID1, 
nodeID1, resources.NewResourceFromMap(map[string]resources.Quantity{"gpu": 
100}))
-               allocN.createTime = askN.createTime
-               app1.AddAllocation(allocN)
-               assert.Check(t, node.TryAddAllocation(allocN), "node alloc1 
failed")
+       tests := []struct {
+               name                  string
+               askCores              int64
+               expectAlloc1Preempted bool
+       }{
+               {"preempt 2 victims - alloc1 protected", 2, false},
+               {"preempt 3 victims with pods dimension", 3, true},
        }
 
-       ask1 := newAllocationAsk("alloc1", appID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
-       ask1.createTime = time.Now().Add(-1 * time.Minute)
-       assert.NilError(t, app1.AddAllocationAsk(ask1))
-       ask2 := newAllocationAsk("alloc2", appID2, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
-       ask2.createTime = time.Now()
-       assert.NilError(t, app1.AddAllocationAsk(ask2))
-       ask3 := newAllocationAsk("alloc3", appID2, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
-       ask3.createTime = time.Now()
-       assert.NilError(t, app1.AddAllocationAsk(ask3))
-       alloc1 := newAllocationWithKey("alloc1", appID1, nodeID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
-       alloc1.createTime = ask1.createTime
-       app1.AddAllocation(alloc1)
-       assert.Check(t, node.TryAddAllocation(alloc1), "node alloc1 failed")
-       alloc2 := newAllocationWithKey("alloc2", appID2, nodeID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
-       alloc2.createTime = ask2.createTime
-       app2.AddAllocation(alloc2)
-       assert.Check(t, node.TryAddAllocation(alloc2), "node alloc2 failed")
-       alloc3 := newAllocationWithKey("alloc3", appID2, nodeID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
-       alloc3.createTime = ask3.createTime
-       app3.AddAllocation(alloc3)
-       assert.Check(t, node.TryAddAllocation(alloc3), "node alloc3 failed")
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       appQueueMapping := NewAppQueueMapping()
+                       node := newNode(nodeID1, 
map[string]resources.Quantity{"vcores": 6, "gpu": 300, "mem": 200, "pods": 10})
+                       iterator := getNodeIteratorFn(node)
+                       rootQ, err := 
createRootQueue(map[string]string{"vcores": "6", "gpu": "300", "mem": "200", 
"pods": "10"})
+                       assert.NilError(t, err)
+                       parentQ, err := createManagedQueueGuaranteed(rootQ, 
"parent", true, nil, nil, appQueueMapping)
+                       assert.NilError(t, err)
+                       parentQ1, err := createManagedQueueGuaranteed(parentQ, 
"parent1", true, nil, nil, appQueueMapping)
+                       assert.NilError(t, err)
+                       parentQ2, err := createManagedQueueGuaranteed(parentQ, 
"parent2", true, nil, nil, appQueueMapping)
+                       assert.NilError(t, err)
 
-       for i := 5; i < 8; i++ {
-               assert.NilError(t, 
childQ2.TryIncAllocatedResource(resources.NewResourceFromMap(map[string]resources.Quantity{"gpu":
 100})))
-       }
-       assert.NilError(t, 
childQ2.TryIncAllocatedResource(ask1.GetAllocatedResource()))
-       assert.NilError(t, 
childQ2.TryIncAllocatedResource(ask2.GetAllocatedResource()))
-       assert.NilError(t, 
childQ2.TryIncAllocatedResource(ask3.GetAllocatedResource()))
+                       childQ1, err := createManagedQueueGuaranteed(parentQ1, 
"child1", false, nil, map[string]string{"vcores": "3"}, appQueueMapping)
+                       assert.NilError(t, err)
+                       childQ2, err := createManagedQueueGuaranteed(parentQ2, 
"child2", false, nil, nil, appQueueMapping)
+                       assert.NilError(t, err)
+                       _, err = createManagedQueueGuaranteed(parentQ2, 
"child3", false, nil, nil, appQueueMapping)
+                       assert.NilError(t, err)
+                       app1, app2, app3 := createVictimApplications(childQ2, 
appQueueMapping)
+                       for i := 5; i < 8; i++ {
+                               askN := newAllocationAsk(alloc+strconv.Itoa(i), 
appID1, resources.NewResourceFromMap(map[string]resources.Quantity{"gpu": 100}))
+                               askN.createTime = time.Now().Add(-2 * 
time.Minute)
+                               assert.NilError(t, app1.AddAllocationAsk(askN))
+                               allocN := 
newAllocationWithKey(askN.allocationKey, appID1, nodeID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"gpu": 100}))
+                               allocN.createTime = askN.createTime
+                               app1.AddAllocation(allocN)
+                               assert.Check(t, node.TryAddAllocation(allocN), 
"node alloc1 failed")
+                       }
 
-       app4 := newApplication("app-4", "default", "root.parent.parent1.child1")
-       app4.SetQueue(childQ1)
-       ask4 := newAllocationAsk("alloc4", "app-4", 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 2, "mem": 
200}))
-       assert.NilError(t, app4.AddAllocationAsk(ask4))
-       headRoom := 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 2})
-       preemptor := NewPreemptor(app4, headRoom, 30*time.Second, ask4, 
iterator(), false)
+                       ask1 := newAllocationAsk("alloc1", appID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1, "pods": 
1}))
+                       ask1.createTime = time.Now().Add(-1 * time.Minute)
+                       assert.NilError(t, app1.AddAllocationAsk(ask1))
+                       ask2 := newAllocationAsk("alloc2", appID2, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1, "pods": 
1}))
+                       ask2.createTime = time.Now()
+                       assert.NilError(t, app1.AddAllocationAsk(ask2))
+                       ask3 := newAllocationAsk("alloc3", appID2, 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1, "pods": 
1}))
+                       ask3.createTime = time.Now()
+                       assert.NilError(t, app1.AddAllocationAsk(ask3))
+                       alloc1 := newAllocationWithKey("alloc1", appID1, 
nodeID1, resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 
1, "pods": 1}))
+                       alloc1.createTime = ask1.createTime
+                       app1.AddAllocation(alloc1)
+                       assert.Check(t, node.TryAddAllocation(alloc1), "node 
alloc1 failed")
+                       alloc2 := newAllocationWithKey("alloc2", appID2, 
nodeID1, resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 
1, "pods": 1}))
+                       alloc2.createTime = ask2.createTime
+                       app2.AddAllocation(alloc2)
+                       assert.Check(t, node.TryAddAllocation(alloc2), "node 
alloc2 failed")
+                       alloc3 := newAllocationWithKey("alloc3", appID2, 
nodeID1, resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 
1, "pods": 1}))
+                       alloc3.createTime = ask3.createTime
+                       app3.AddAllocation(alloc3)
+                       assert.Check(t, node.TryAddAllocation(alloc3), "node 
alloc3 failed")
 
-       // register predicate handler
-       plugin := mock.NewPreemptionPredicatePlugin(nil, nil, false, false)
-       plugins.RegisterSchedulerPlugin(plugin)
-       defer plugins.UnregisterSchedulerPlugins()
+                       for i := 5; i < 8; i++ {
+                               assert.NilError(t, 
childQ2.TryIncAllocatedResource(resources.NewResourceFromMap(map[string]resources.Quantity{"gpu":
 100})))
+                       }
+                       assert.NilError(t, 
childQ2.TryIncAllocatedResource(ask1.GetAllocatedResource()))
+                       assert.NilError(t, 
childQ2.TryIncAllocatedResource(ask2.GetAllocatedResource()))
+                       assert.NilError(t, 
childQ2.TryIncAllocatedResource(ask3.GetAllocatedResource()))
+
+                       app4 := newApplication("app-4", "default", 
"root.parent.parent1.child1")
+                       app4.SetQueue(childQ1)
+                       ask4 := newAllocationAsk("alloc4", "app-4", 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 
resources.Quantity(tt.askCores), "mem": 200, "pods": 1}))
+                       assert.NilError(t, app4.AddAllocationAsk(ask4))
+                       headRoom := 
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 
resources.Quantity(tt.askCores), "pods": 1})
+                       preemptor := NewPreemptor(app4, headRoom, 
30*time.Second, ask4, iterator(), false)
+
+                       // register predicate handler
+                       plugin := mock.NewPreemptionPredicatePlugin(nil, nil, 
false, false)
+                       plugins.RegisterSchedulerPlugin(plugin)
+                       defer plugins.UnregisterSchedulerPlugins()
 
-       result, ok := preemptor.TryPreemption()
-       assert.Assert(t, result != nil, "no result")
-       assert.NilError(t, plugin.GetPredicateError())
-       assert.Assert(t, ok, "no victims found")
-       assert.Equal(t, "alloc4", result.Request.GetAllocationKey(), "wrong 
alloc")
-       assert.Equal(t, nodeID1, result.NodeID, "wrong node")
-       assert.Equal(t, nodeID1, alloc2.nodeID, "wrong node")
-       assert.Equal(t, nodeID1, alloc3.nodeID, "wrong node")
-       assert.Check(t, !alloc1.IsPreempted(), "alloc1 preempted")
-       assert.Check(t, alloc2.IsPreempted(), "alloc2 not preempted")
-       assert.Check(t, alloc3.IsPreempted(), "alloc3 not preempted")
-       assert.Equal(t, len(ask4.GetAllocationLog()), 0)
+                       result, ok := preemptor.TryPreemption()
+                       assert.Assert(t, result != nil, "no result")
+                       assert.NilError(t, plugin.GetPredicateError())
+                       assert.Assert(t, ok, "no victims found")
+                       assert.Equal(t, "alloc4", 
result.Request.GetAllocationKey(), "wrong alloc")
+                       assert.Equal(t, nodeID1, result.NodeID, "wrong node")
+                       assert.Equal(t, nodeID1, alloc2.nodeID, "wrong node")
+                       assert.Equal(t, nodeID1, alloc3.nodeID, "wrong node")
+                       assert.Check(t, alloc2.IsPreempted(), "alloc2 not 
preempted")
+                       assert.Check(t, alloc3.IsPreempted(), "alloc3 not 
preempted")
+                       if tt.expectAlloc1Preempted {
+                               assert.Equal(t, nodeID1, alloc1.nodeID, "wrong 
node")
+                               assert.Check(t, alloc1.IsPreempted(), "alloc1 
not preempted")
+                       } else {
+                               assert.Check(t, !alloc1.IsPreempted(), "alloc1 
preempted")
+                       }
+                       assert.Equal(t, len(ask4.GetAllocationLog()), 0)
+               })
+       }
 }
 
 // TestTryPreemption_OnNode_AskResTypesSame_GuaranteedSetOnPreemptorSide Test 
try preemption with 2 level queue hierarchy. Since Node doesn't have enough 
resources to accomodate, preemption happens because of node resource constraint.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to