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

ccondit 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 fd411a5f [YUNIKORN-2769] Properly handle preemption betweeen sibling 
queues (#923)
fd411a5f is described below

commit fd411a5fa61f1e645e2a16f2c0a6fc75bf6c19b6
Author: Manikandan R <[email protected]>
AuthorDate: Mon Aug 12 15:00:39 2024 -0500

    [YUNIKORN-2769] Properly handle preemption betweeen sibling queues (#923)
    
    Handle the case where preemptor is under guaranteed limit and parent is
    above guaranteed limits.
    
    Closes: #923
    
    Signed-off-by: Craig Condit <[email protected]>
---
 pkg/common/resources/resources.go              |  62 +++++-
 pkg/common/resources/resources_test.go         |  65 +++++++
 pkg/scheduler/objects/preemption.go            |   8 +
 pkg/scheduler/objects/preemption_queue_test.go | 249 ++++++++++++++++---------
 pkg/scheduler/objects/preemption_test.go       |  65 +++++++
 pkg/scheduler/objects/queue.go                 |  23 ++-
 6 files changed, 367 insertions(+), 105 deletions(-)

diff --git a/pkg/common/resources/resources.go 
b/pkg/common/resources/resources.go
index 23cad7b5..1b2a036b 100644
--- a/pkg/common/resources/resources.go
+++ b/pkg/common/resources/resources.go
@@ -629,12 +629,11 @@ func compareShares(lshares, rshares []float64) int {
        return 0
 }
 
-// Compare the resources equal returns the specific values for following cases:
-// left  right  return
-// nil   nil    true
-// nil   <set>  false
-// <set> nil    false
-// <set> <set>  true/false  *based on the individual Quantity values
+// Equals Compare the resources based on common resource type available in 
both left and right Resource
+// Resource type available in left Resource but not in right Resource and vice 
versa is not taken into account
+// False in case anyone of the resources is nil
+// False in case resource type value differs
+// True in case when resource type values of left Resource matches with right 
Resource if resource type is available
 func Equals(left, right *Resource) bool {
        if left == right {
                return true
@@ -649,13 +648,39 @@ func Equals(left, right *Resource) bool {
                        return false
                }
        }
-
        for k, v := range right.Resources {
                if left.Resources[k] != v {
                        return false
                }
        }
+       return true
+}
 
+// DeepEquals Compare the resources based on resource type existence and its 
values as well
+// False in case anyone of the resources is nil
+// False in case resource length differs
+// False in case resource type existed in left Resource not exist in right 
Resource
+// False in case resource type value differs
+// True in case when all resource type and its values of left Resource matches 
with right Resource
+func DeepEquals(left, right *Resource) bool {
+       if left == right {
+               return true
+       }
+       if left == nil || right == nil {
+               return false
+       }
+       if len(right.Resources) != len(left.Resources) {
+               return false
+       }
+       for k, v := range left.Resources {
+               if val, ok := right.Resources[k]; ok {
+                       if val != v {
+                               return false
+                       }
+               } else {
+                       return false
+               }
+       }
        return true
 }
 
@@ -882,6 +907,29 @@ func ComponentWiseMin(left, right *Resource) *Resource {
        return out
 }
 
+// MergeIfNotPresent Returns a new Resource by merging resource type values 
present in right with left
+// only if resource type not present in left.
+// If either Resource passed in is nil the other Resource is returned
+// If a Resource type is missing from one of the Resource, it is considered 
empty and the quantity from the other Resource is returned
+func MergeIfNotPresent(left, right *Resource) *Resource {
+       if right == nil && left == nil {
+               return nil
+       }
+       if left == nil {
+               return right.Clone()
+       }
+       if right == nil {
+               return left.Clone()
+       }
+       out := left.Clone()
+       for k, v := range right.Resources {
+               if _, ok := left.Resources[k]; !ok {
+                       out.Resources[k] = v
+               }
+       }
+       return out
+}
+
 // ComponentWiseMinOnlyExisting Returns a new Resource with the smallest value 
for resource type
 // existing only in left but not vice versa.
 func ComponentWiseMinOnlyExisting(left, right *Resource) *Resource {
diff --git a/pkg/common/resources/resources_test.go 
b/pkg/common/resources/resources_test.go
index 05e603a7..49df426a 100644
--- a/pkg/common/resources/resources_test.go
+++ b/pkg/common/resources/resources_test.go
@@ -532,6 +532,46 @@ func TestComponentWiseMinOnlyExisting(t *testing.T) {
        }
 }
 
+func TestMergeIfNotPresent(t *testing.T) {
+       testCases := []struct {
+               name     string
+               left     map[string]Quantity
+               right    map[string]Quantity
+               expected map[string]Quantity
+       }{
+               {"Min of nil resources should be nil", nil, nil, nil},
+               {"Min of empty resources should be empty resource ", 
map[string]Quantity{}, map[string]Quantity{}, map[string]Quantity{}},
+               {"Min of positive resource and nil resource", 
map[string]Quantity{"first": 5}, nil, map[string]Quantity{"first": 5}},
+               {"Min of nil resource and positive resource", nil, 
map[string]Quantity{"first": 5}, map[string]Quantity{"first": 5}},
+               {"Min of two positive resources", map[string]Quantity{"first": 
5}, map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5}},
+               {"Min of two positive resources", map[string]Quantity{"first": 
10}, map[string]Quantity{"first": 5}, map[string]Quantity{"first": 10}},
+               {"Min of positive resource and negative resource", 
map[string]Quantity{"first": 5}, map[string]Quantity{"first": -5}, 
map[string]Quantity{"first": 5}},
+               {"Min of positive resource and negative resource", 
map[string]Quantity{"first": -5}, map[string]Quantity{"first": 5}, 
map[string]Quantity{"first": -5}},
+               {"Min of two positive resources with extra resource types", 
map[string]Quantity{"first": 10}, map[string]Quantity{"first": 5, "second": 
15}, map[string]Quantity{"first": 10, "second": 15}},
+               {"Min of two positive resources with extra resource types", 
map[string]Quantity{"first": 5, "second": 15}, map[string]Quantity{"first": 
10}, map[string]Quantity{"first": 5, "second": 15}},
+               {"Min of positive resource and negative resource with extra 
resource types", map[string]Quantity{"first": 10}, map[string]Quantity{"first": 
-5, "second": 15}, map[string]Quantity{"first": 10, "second": 15}},
+               {"Min of positive resource and negative resource with extra 
resource types", map[string]Quantity{"first": -5, "second": 15}, 
map[string]Quantity{"first": 10}, map[string]Quantity{"first": -5, "second": 
15}},
+       }
+       for _, tc := range testCases {
+               var left *Resource
+               var right *Resource
+               var expected *Resource
+               if tc.left != nil {
+                       left = NewResourceFromMap(tc.left)
+               }
+               if tc.right != nil {
+                       right = NewResourceFromMap(tc.right)
+               }
+               if tc.expected != nil {
+                       expected = NewResourceFromMap(tc.expected)
+               }
+               t.Run(tc.name, func(t *testing.T) {
+                       result := MergeIfNotPresent(left, right)
+                       assert.DeepEqual(t, result, expected)
+               })
+       }
+}
+
 func TestComponentWiseMax(t *testing.T) {
        type inputs struct {
                res1    map[string]Quantity
@@ -1157,6 +1197,31 @@ func TestEqualsOrEmpty(t *testing.T) {
        }
 }
 
+func TestDeepEquals(t *testing.T) {
+       var tests = []struct {
+               left, right *Resource
+               want        bool
+       }{
+               {nil, nil, true},
+               {nil, NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), 
false},
+               {NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), nil, 
false},
+               {nil, NewResource(), false},
+               {NewResource(), nil, false},
+               {NewResourceFromMap(map[string]Quantity{"a": 0}), 
NewResourceFromMap(map[string]Quantity{"a": 0}), true},
+               {NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), 
NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), true},
+               {NewResourceFromMap(map[string]Quantity{"a": 0}), 
NewResourceFromMap(map[string]Quantity{"b": 0, "c": 1}), false},
+               {NewResourceFromMap(map[string]Quantity{"a": 0, "b": 1}), 
NewResourceFromMap(map[string]Quantity{"a": 1, "b": 1}), false},
+               {NewResourceFromMap(map[string]Quantity{"a": 0, "c": 1}), 
NewResourceFromMap(map[string]Quantity{"a": 0, "d": 3}), false},
+               {NewResourceFromMap(map[string]Quantity{"a": 0}), 
NewResourceFromMap(map[string]Quantity{"d": 0}), false},
+       }
+
+       for _, tt := range tests {
+               if got := DeepEquals(tt.left, tt.right); got != tt.want {
+                       t.Errorf("got %v, want %v", got, tt.want)
+               }
+       }
+}
+
 func TestFitIn(t *testing.T) {
        tests := []struct {
                name    string
diff --git a/pkg/scheduler/objects/preemption.go 
b/pkg/scheduler/objects/preemption.go
index 3303a064..6b00edab 100644
--- a/pkg/scheduler/objects/preemption.go
+++ b/pkg/scheduler/objects/preemption.go
@@ -69,6 +69,7 @@ type QueuePreemptionSnapshot struct {
        MaxResource        *resources.Resource      // maximum resources for 
this queue
        GuaranteedResource *resources.Resource      // guaranteed resources for 
this queue
        PotentialVictims   []*Allocation            // list of allocations 
which could be preempted
+       AskQueue           *QueuePreemptionSnapshot // snapshot of ask or 
preemptor queue
 }
 
 // NewPreemptor creates a new preemptor. The preemptor itself is not thread 
safe, and assumes the application lock is held.
@@ -760,6 +761,7 @@ func (qps *QueuePreemptionSnapshot) Duplicate(copy 
map[string]*QueuePreemptionSn
                MaxResource:        qps.MaxResource.Clone(),
                GuaranteedResource: qps.GuaranteedResource.Clone(),
                PotentialVictims:   qps.PotentialVictims,
+               AskQueue:           qps.AskQueue,
        }
        copy[qps.QueuePath] = snapshot
        return snapshot
@@ -825,6 +827,12 @@ func (qps *QueuePreemptionSnapshot) 
GetRemainingGuaranteedResource() *resources.
        used := qps.AllocatedResource.Clone()
        used.SubOnlyExisting(qps.PreemptingResource)
        remainingGuaranteed.SubOnlyExisting(used)
+       if qps.AskQueue != nil {
+               // In case ask queue has guaranteed set, its own values carries 
higher precedence over the parent or ancestor
+               if qps.AskQueue.QueuePath == qps.QueuePath && 
!remainingGuaranteed.IsEmpty() {
+                       return resources.MergeIfNotPresent(remainingGuaranteed, 
parent)
+               }
+       }
        return resources.ComponentWiseMin(remainingGuaranteed, parent)
 }
 
diff --git a/pkg/scheduler/objects/preemption_queue_test.go 
b/pkg/scheduler/objects/preemption_queue_test.go
index 0baaffaf..90af333f 100644
--- a/pkg/scheduler/objects/preemption_queue_test.go
+++ b/pkg/scheduler/objects/preemption_queue_test.go
@@ -123,113 +123,186 @@ func TestGetPreemptableResource(t *testing.T) {
 
 func TestGetRemainingGuaranteedResource(t *testing.T) {
        // no guaranteed and no usage. so no remaining
+       rootQ, parentQ, childQ1, childQ2, childQ3 := setup(t)
+       smallestRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5})
+       childRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"second": 5})
+       expectedSmallestRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 3})
+       expectedSmallestRes1 := smallestRes.Clone()
+       expectedSmallestRes1.MultiplyTo(float64(0))
+       var tests = []struct {
+               testName          string
+               askQueue          *Queue
+               childQ2Remaining  *resources.Resource
+               childQ2Remaining1 *resources.Resource
+       }{
+               
{"UnderGuaranteedChildQueue_Under_OverGuaranteedParentQueue_Does_Not_Have_Higher_Precedence_When_AskQueue_Is_Different_From_UnderGuaranteedChildQueue",
 childQ3,
+                       resources.Multiply(smallestRes, -1), 
resources.Add(expectedSmallestRes1, resources.Multiply(childRes, -1))},
+               
{"UnderGuaranteedChildQueue_Under_OverGuaranteedParentQueue_Has_Higher_Precedence_When_AskQueue_Is_Same_As_UnderGuaranteedChildQueue",
 childQ2,
+                       resources.Multiply(smallestRes, 0), 
resources.Add(expectedSmallestRes, resources.Multiply(childRes, -1))},
+       }
+       for _, tt := range tests {
+               var rootRemaining, pRemaining, cRemaining1, cRemaining2 
*resources.Resource
+               resetQueueResources(rootQ, parentQ, childQ1, childQ2)
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, 
map[string]*resources.Resource{rootQ.QueuePath: nil, parentQ.QueuePath: nil, 
childQ1.QueuePath: nil, childQ2.QueuePath: nil}, tt.askQueue)
+               assertZeroRemaining(t, rootRemaining, pRemaining, cRemaining1, 
cRemaining2)
+
+               // no guaranteed and no usage, but max res set. so min of 
guaranteed and max should be remaining
+               smallestRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5})
+               rootQ.maxResource = resources.Multiply(smallestRes, 4)
+               parentQ.maxResource = resources.Multiply(smallestRes, 2)
+               childQ1.maxResource = smallestRes
+               childQ2.maxResource = smallestRes
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, 
map[string]*resources.Resource{rootQ.QueuePath: nil, parentQ.QueuePath: nil, 
childQ1.QueuePath: nil, childQ2.QueuePath: nil}, tt.askQueue)
+               assertZeroRemaining(t, rootRemaining, pRemaining, cRemaining1, 
cRemaining2)
+
+               // guaranteed set only for queue at specific levels but no 
usage.
+               // so remaining for queues without guaranteed quota inherits 
from parent queue based on min perm calculation
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, 
map[string]*resources.Resource{rootQ.QueuePath: resources.Multiply(smallestRes, 
2), parentQ.QueuePath: nil, childQ1.QueuePath: childRes, childQ2.QueuePath: 
nil}, tt.askQueue)
+               assert.Assert(t, resources.Equals(rootRemaining, 
resources.Multiply(smallestRes, 2)), "guaranteed set, but no usage. so all 
guaranteed should be in remaining")
+               assert.Assert(t, resources.Equals(pRemaining, 
resources.Multiply(smallestRes, 2)), "guaranteed not set, also no usage. 
However, parent's remaining should be used")
+               assert.Assert(t, resources.Equals(cRemaining1, 
resources.Add(resources.Multiply(smallestRes, 2), childRes)), "guaranteed not 
set, also no usage. However, parent's remaining should be used")
+               assert.Assert(t, resources.Equals(cRemaining2, 
resources.Multiply(smallestRes, 2)), "guaranteed not set, also no usage. 
However, parent's remaining should be used")
+
+               // guaranteed set but no usage. so nothing to preempt
+               // clean start for the snapshot: whole hierarchy with guarantee
+               queueRes := map[string]*resources.Resource{rootQ.QueuePath: 
resources.Multiply(smallestRes, 2), parentQ.QueuePath: smallestRes, 
childQ1.QueuePath: childRes, childQ2.QueuePath: smallestRes}
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, queueRes, 
tt.askQueue)
+               assert.Assert(t, resources.Equals(rootRemaining, 
resources.Multiply(smallestRes, 2)), "guaranteed set, but no usage. so all 
guaranteed should be in remaining")
+               assert.Assert(t, resources.Equals(pRemaining, smallestRes), 
"guaranteed set, but no usage. so all guaranteed should be in remaining")
+               assert.Assert(t, resources.Equals(cRemaining1, 
resources.Add(smallestRes, childRes)), "guaranteed set, but no usage. so all 
guaranteed + parent remaining guaranteed should be in remaining")
+               assert.Assert(t, resources.Equals(cRemaining2, smallestRes), 
"guaranteed set, but no usage. so all its guaranteed (because it is lesser than 
parent's guaranteed) should be in remaining")
+
+               // clean start for the snapshot: all set guaranteed
+               // add usage to parent + root: use all guaranteed at parent 
level
+               // add usage to child2: use all guaranteed set
+               // child2 remaining behaviour changes based on the ask queue.
+               // When ask queue is child2, its own values has higher 
precedence over the parent or ancestor for common resource types.
+               // for extra resources available in parent or ancestor, it can 
simply inherit.
+               // When ask queue is child3 (diverged from very earlier branch, 
not sharing any common queue path), its remaining is min permissive of its own 
values and parent or ancestor values.
+               rootQ.allocatedResource = resources.Multiply(smallestRes, 2)
+               parentQ.allocatedResource = resources.Multiply(smallestRes, 2)
+               childQ2.allocatedResource = resources.Multiply(smallestRes, 1)
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, queueRes, 
tt.askQueue)
+               assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed 
set, used completely. so all guaranteed should be in remaining")
+               assert.Assert(t, resources.Equals(pRemaining, 
resources.Multiply(smallestRes, -1)), "guaranteed set, used double than 
guaranteed. so remaining should be in -ve")
+               assert.Assert(t, resources.Equals(cRemaining1, 
resources.Add(resources.Multiply(smallestRes, -1), childRes)), "guaranteed set, 
but no usage. However remaining should include its all guaranteed + parent 
remaining guaranteed")
+               assert.Assert(t, resources.Equals(cRemaining2, 
tt.childQ2Remaining), "guaranteed set, used all guaranteed. remaining should be 
based on ask queue")
+
+               // clean start for the snapshot: all set guaranteed
+               // add usage for all: use exactly guaranteed at parent and 
child level
+               // parent guarantee used for one type child guarantee used for 
second type
+               bothRes := resources.Multiply(smallestRes, 2)
+               bothRes.AddTo(childRes)
+               rootQ.allocatedResource = bothRes
+               bothRes = resources.Add(smallestRes, childRes)
+               parentQ.allocatedResource = bothRes
+               childQ1.allocatedResource = childRes
+               childQ2.allocatedResource = smallestRes
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, queueRes, 
tt.askQueue)
+               assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed 
set, used completely. usage also has extra resource types. However, no 
remaining")
+               assert.Assert(t, resources.IsZero(pRemaining), "guaranteed set, 
used completely. usage also has extra resource types. However, no remaining")
+               assert.Assert(t, resources.IsZero(cRemaining1), "guaranteed 
set, used completely. so, no remaining")
+               assert.Assert(t, resources.IsZero(cRemaining2), "guaranteed 
set, but no usage. Still, no remaining in guaranteed because of its parent 
queue")
+
+               // clean start for the snapshot: all set guaranteed
+               // add usage for root + parent: use exactly guaranteed at 
parent and child level
+               // add usage to child1: use double than guaranteed
+               // parent guarantee used for one type child guarantee used for 
second type
+               bothRes = resources.Multiply(smallestRes, 2)
+               bothRes.AddTo(resources.Multiply(childRes, 2))
+               rootQ.allocatedResource = bothRes
+               bothRes = resources.Add(smallestRes, 
resources.Multiply(childRes, 2))
+               parentQ.allocatedResource = bothRes
+               childQ1.allocatedResource = resources.Multiply(childRes, 2)
+               childQ2.allocatedResource = smallestRes
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, queueRes, 
tt.askQueue)
+               assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed 
set, used completely. usage also has extra resource types. However, no 
remaining")
+               assert.Assert(t, resources.IsZero(pRemaining), "guaranteed set, 
used completely. usage also has extra resource types. However, no remaining")
+               assert.Assert(t, resources.Equals(cRemaining1, 
resources.Multiply(childRes, -1)), "guaranteed set, used double than 
guaranteed. so remaining should be in -ve")
+               assert.Assert(t, resources.IsZero(cRemaining2), "guaranteed 
set, but no usage. Still, no remaining in guaranteed because of its parent 
queue")
+
+               // clean start for the snapshot: all set guaranteed
+               // add usage for root + parent: use exactly guaranteed for one 
resource and over guaranteed for another resource at parent level
+               // add usage to child1: use double than guaranteed
+               // add usage to child2: use lesser than guaranteed.
+               // child2 remaining behaviour changes based on the ask queue.
+               // When ask queue is child2, its own values has higher 
precedence over the parent or ancestor for common resource types.
+               // for extra resources available in parent or ancestor, it can 
simply inherit.
+               // When ask queue is child3 (diverged from very earlier branch, 
not sharing any common queue path), its remaining is min permissive of its own 
values and parent or ancestor values.
+               childQ2.allocatedResource = 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2})
+               rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
setAndGetRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2, 
map[string]*resources.Resource{rootQ.QueuePath: resources.Multiply(smallestRes, 
2), parentQ.QueuePath: resources.Add(smallestRes, resources.Multiply(childRes, 
1)), childQ1.QueuePath: childRes, childQ2.QueuePath: smallestRes}, tt.askQueue)
+               assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed 
set, used completely. usage also has extra resource types. However, no 
remaining")
+               assert.Assert(t, resources.DeepEquals(pRemaining, 
resources.Add(expectedSmallestRes1, resources.Multiply(childRes, -1))), 
"guaranteed set, one resource type used completely. usage also has another 
resource types which is used bit more. remaining should have zero for one 
resource type and -ve for another")
+               assert.Assert(t, resources.DeepEquals(cRemaining1, 
resources.Add(expectedSmallestRes1, resources.Multiply(childRes, -1))), 
"guaranteed set, used double than guaranteed. so remaining should be in -ve")
+               assert.Assert(t, resources.DeepEquals(cRemaining2, 
tt.childQ2Remaining1), "guaranteed set, used bit lesser. parent's usage also 
has extra resource types. remaining should be based on ask queue")
+       }
+}
+
+func setup(t *testing.T) (rootQ, parentQ, childQ1, childQ2, childQ3 *Queue) {
        rootQ, err := createRootQueue(map[string]string{})
        assert.NilError(t, err)
-       var parentQ, childQ1, childQ2 *Queue
+       var parent1Q *Queue
        parentQ, err = createManagedQueue(rootQ, "parent", true, 
map[string]string{})
        assert.NilError(t, err)
+       parent1Q, err = createManagedQueue(rootQ, "parent1", true, 
map[string]string{})
+       assert.NilError(t, err)
        childQ1, err = createManagedQueue(parentQ, "child1", false, 
map[string]string{})
        assert.NilError(t, err)
        childQ2, err = createManagedQueue(parentQ, "child2", false, 
map[string]string{})
        assert.NilError(t, err)
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 := 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
-       assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed and max 
res not set, so no remaining")
-       assert.Assert(t, resources.IsZero(pRemaining), "guaranteed and max res 
not set, so no remaining")
-       assert.Assert(t, resources.IsZero(cRemaining1), "guaranteed and max res 
not set, so no remaining")
-       assert.Assert(t, resources.IsZero(cRemaining2), "guaranteed and max res 
not set, so no remaining")
+       childQ3, err = createManagedQueue(parent1Q, "child3", false, 
map[string]string{})
+       assert.NilError(t, err)
+       return
+}
 
-       // no guaranteed and no usage, but max res set. so min of guaranteed 
and max should be remaining
-       smallestRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5})
-       rootQ.maxResource = resources.Multiply(smallestRes, 4)
-       parentQ.maxResource = resources.Multiply(smallestRes, 2)
-       childQ1.maxResource = smallestRes
-       childQ2.maxResource = smallestRes
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
+func assertZeroRemaining(t *testing.T, rootRemaining *resources.Resource, 
pRemaining *resources.Resource, cRemaining1 *resources.Resource, cRemaining2 
*resources.Resource) {
        assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed and max 
res not set, so no remaining")
        assert.Assert(t, resources.IsZero(pRemaining), "guaranteed and max res 
not set, so no remaining")
        assert.Assert(t, resources.IsZero(cRemaining1), "guaranteed and max res 
not set, so no remaining")
        assert.Assert(t, resources.IsZero(cRemaining2), "guaranteed and max res 
not set, so no remaining")
+}
 
-       // guaranteed set only for queue at specific levels but no usage.
-       // so remaining for queues without guaranteed quota inherits from 
parent queue based on min perm calculation
-       childRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"second": 5})
-       rootQ.guaranteedResource = resources.Multiply(smallestRes, 2)
-       childQ1.guaranteedResource = childRes
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
-       assert.Assert(t, resources.Equals(rootRemaining, 
resources.Multiply(smallestRes, 2)), "guaranteed set, but no usage. so all 
guaranteed should be in remaining")
-       assert.Assert(t, resources.Equals(pRemaining, 
resources.Multiply(smallestRes, 2)), "guaranteed not set, also no usage. 
However, parent's remaining should be used")
-       assert.Assert(t, resources.Equals(cRemaining1, 
resources.Add(resources.Multiply(smallestRes, 2), childRes)), "guaranteed not 
set, also no usage. However, parent's remaining should be used")
-       assert.Assert(t, resources.Equals(cRemaining2, 
resources.Multiply(smallestRes, 2)), "guaranteed not set, also no usage. 
However, parent's remaining should be used")
-
-       // guaranteed set but no usage. so nothing to preempt
-       // clean start for the snapshot: whole hierarchy with guarantee
-       rootQ.guaranteedResource = resources.Multiply(smallestRes, 2)
-       parentQ.guaranteedResource = smallestRes
-       childQ2.guaranteedResource = smallestRes
-       childQ1.guaranteedResource = childRes
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
-       assert.Assert(t, resources.Equals(rootRemaining, 
resources.Multiply(smallestRes, 2)), "guaranteed set, but no usage. so all 
guaranteed should be in remaining")
-       assert.Assert(t, resources.Equals(pRemaining, smallestRes), "guaranteed 
set, but no usage. so all guaranteed should be in remaining")
-       assert.Assert(t, resources.Equals(cRemaining1, 
resources.Add(smallestRes, childRes)), "guaranteed set, but no usage. so all 
guaranteed + parent remaining guaranteed should be in remaining")
-       assert.Assert(t, resources.Equals(cRemaining2, smallestRes), 
"guaranteed set, but no usage. so all its guaranteed (because it is lesser than 
parent's guaranteed) should be in remaining")
-
-       // clean start for the snapshot: all set guaranteed
-       // add usage to parent + root: use all guaranteed at parent level
-       // add usage to child2: use double than guaranteed
-       rootQ.allocatedResource = resources.Multiply(smallestRes, 2)
-       parentQ.allocatedResource = resources.Multiply(smallestRes, 2)
-       childQ2.allocatedResource = resources.Multiply(smallestRes, 2)
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
-       assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed set, used 
completely. so all guaranteed should be in remaining")
-       assert.Assert(t, resources.Equals(pRemaining, 
resources.Multiply(smallestRes, -1)), "guaranteed set, used double than 
guaranteed. so remaining should be in -ve")
-       assert.Assert(t, resources.Equals(cRemaining1, 
resources.Add(resources.Multiply(smallestRes, -1), childRes)), "guaranteed set, 
but no usage. However remaining should include its all guaranteed + parent 
remaining guaranteed")
-       assert.Assert(t, resources.Equals(cRemaining2, 
resources.Multiply(smallestRes, -1)), "guaranteed set, used double than 
guaranteed. so remaining should be in -ve")
-
-       // clean start for the snapshot: all set guaranteed
-       // add usage for all: use exactly guaranteed at parent and child level
-       // parent guarantee used for one type child guarantee used for second 
type
-       bothRes := resources.Multiply(smallestRes, 2)
-       bothRes.AddTo(childRes)
-       rootQ.allocatedResource = bothRes
-       bothRes = resources.Add(smallestRes, childRes)
-       parentQ.allocatedResource = bothRes
-       childQ1.allocatedResource = childRes
-       childQ2.allocatedResource = smallestRes
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
-       assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed set, used 
completely. usage also has extra resource types. However, no remaining")
-       assert.Assert(t, resources.IsZero(pRemaining), "guaranteed set, used 
completely. usage also has extra resource types. However, no remaining")
-       assert.Assert(t, resources.IsZero(cRemaining1), "guaranteed set, used 
completely. so, no remaining")
-       assert.Assert(t, resources.IsZero(cRemaining2), "guaranteed set, but no 
usage. Still, no remaining in guaranteed because of its parent queue")
-
-       // clean start for the snapshot: all set guaranteed
-       // add usage for root + parent: use exactly guaranteed at parent and 
child level
-       // add usage to child1: use double than guaranteed
-       // parent guarantee used for one type child guarantee used for second 
type
-       bothRes = resources.Multiply(smallestRes, 2)
-       bothRes.AddTo(resources.Multiply(childRes, 2))
-       rootQ.allocatedResource = bothRes
-       bothRes = resources.Add(smallestRes, resources.Multiply(childRes, 2))
-       parentQ.allocatedResource = bothRes
-       childQ1.allocatedResource = resources.Multiply(childRes, 2)
-       childQ2.allocatedResource = smallestRes
-       rootRemaining, pRemaining, cRemaining1, cRemaining2 = 
getRemainingGuaranteed(rootQ, parentQ, childQ1, childQ2)
-       assert.Assert(t, resources.IsZero(rootRemaining), "guaranteed set, used 
completely. usage also has extra resource types. However, no remaining")
-       assert.Assert(t, resources.IsZero(pRemaining), "guaranteed set, used 
completely. usage also has extra resource types. However, no remaining")
-       assert.Assert(t, resources.Equals(cRemaining1, 
resources.Multiply(childRes, -1)), "guaranteed set, used double than 
guaranteed. so remaining should be in -ve")
-       assert.Assert(t, resources.IsZero(cRemaining2), "guaranteed set, but no 
usage. Still, no remaining in guaranteed because of its parent queue")
+func resetQueueResources(rootQ *Queue, parentQ *Queue, childQ1 *Queue, childQ2 
*Queue) {
+       rootQ.guaranteedResource = nil
+       parentQ.guaranteedResource = nil
+       childQ1.guaranteedResource = nil
+       childQ2.guaranteedResource = nil
+       rootQ.allocatedResource = nil
+       parentQ.allocatedResource = nil
+       childQ1.allocatedResource = nil
+       childQ2.allocatedResource = nil
+       rootQ.preemptingResource = nil
+       parentQ.preemptingResource = nil
+       childQ1.preemptingResource = nil
+       childQ2.preemptingResource = nil
 }
 
-func createQPSCache(rootQ *Queue, parentQ *Queue, childQ1 *Queue, childQ2 
*Queue) (*QueuePreemptionSnapshot, *QueuePreemptionSnapshot, 
*QueuePreemptionSnapshot, *QueuePreemptionSnapshot) {
+func createQPSCache(rootQ *Queue, parentQ *Queue, childQ1 *Queue, childQ2 
*Queue, askQueue *Queue) (*QueuePreemptionSnapshot, *QueuePreemptionSnapshot, 
*QueuePreemptionSnapshot, *QueuePreemptionSnapshot) {
        cache := make(map[string]*QueuePreemptionSnapshot)
-       qpsRoot := rootQ.createPreemptionSnapshot(cache)
-       qpsParent := parentQ.createPreemptionSnapshot(cache)
-       qpsChild1 := childQ1.createPreemptionSnapshot(cache)
-       qpsChild2 := childQ2.createPreemptionSnapshot(cache)
+       askQueuePath := ""
+       if askQueue != nil {
+               askQueuePath = askQueue.QueuePath
+               askQueue.createPreemptionSnapshot(cache, askQueue.QueuePath)
+               c := askQueue
+               // set the ask queue for all queues in the ask queue hierarchy
+               for c.parent != nil {
+                       cache[c.QueuePath].AskQueue = cache[askQueue.QueuePath]
+                       c = c.parent
+               }
+       }
+       qpsRoot := rootQ.createPreemptionSnapshot(cache, askQueuePath)
+       qpsParent := parentQ.createPreemptionSnapshot(cache, askQueuePath)
+       qpsChild1 := childQ1.createPreemptionSnapshot(cache, askQueuePath)
+       qpsChild2 := childQ2.createPreemptionSnapshot(cache, askQueuePath)
        return qpsRoot, qpsParent, qpsChild1, qpsChild2
 }
 
-func getRemainingGuaranteed(rootQ *Queue, parentQ *Queue, childQ1 *Queue, 
childQ2 *Queue) (*resources.Resource, *resources.Resource, *resources.Resource, 
*resources.Resource) {
-       qpsRoot, qpsParent, qpsChild1, qpsChild2 := createQPSCache(rootQ, 
parentQ, childQ1, childQ2)
+func setAndGetRemainingGuaranteed(rootQ *Queue, parentQ *Queue, childQ1 
*Queue, childQ2 *Queue, queueRes map[string]*resources.Resource, askQueue 
*Queue) (*resources.Resource, *resources.Resource, *resources.Resource, 
*resources.Resource) {
+       rootQ.guaranteedResource = queueRes[rootQ.QueuePath]
+       parentQ.guaranteedResource = queueRes[parentQ.QueuePath]
+       childQ2.guaranteedResource = queueRes[childQ2.QueuePath]
+       childQ1.guaranteedResource = queueRes[childQ1.QueuePath]
+       qpsRoot, qpsParent, qpsChild1, qpsChild2 := createQPSCache(rootQ, 
parentQ, childQ1, childQ2, askQueue)
        rootRemaining := qpsRoot.GetRemainingGuaranteedResource()
        pRemaining := qpsParent.GetRemainingGuaranteedResource()
        cRemaining1 := qpsChild1.GetRemainingGuaranteedResource()
@@ -238,7 +311,7 @@ func getRemainingGuaranteed(rootQ *Queue, parentQ *Queue, 
childQ1 *Queue, childQ
 }
 
 func getPreemptableResource(rootQ *Queue, parentQ *Queue, childQ1 *Queue, 
childQ2 *Queue) (*resources.Resource, *resources.Resource, *resources.Resource, 
*resources.Resource) {
-       qpsRoot, qpsParent, qpsChild1, qpsChild2 := createQPSCache(rootQ, 
parentQ, childQ1, childQ2)
+       qpsRoot, qpsParent, qpsChild1, qpsChild2 := createQPSCache(rootQ, 
parentQ, childQ1, childQ2, nil)
        rootRemaining := qpsRoot.GetPreemptableResource()
        pRemaining := qpsParent.GetPreemptableResource()
        cRemaining1 := qpsChild1.GetPreemptableResource()
diff --git a/pkg/scheduler/objects/preemption_test.go 
b/pkg/scheduler/objects/preemption_test.go
index a7903e5c..04eb6a05 100644
--- a/pkg/scheduler/objects/preemption_test.go
+++ b/pkg/scheduler/objects/preemption_test.go
@@ -292,6 +292,71 @@ func TestTryPreemptionOnNode(t *testing.T) {
        assert.Check(t, alloc2.IsPreempted(), "alloc2 not preempted")
 }
 
+// TestTryPreemptionOnNodeWithOGParentAndUGPreemptor Test try preemption on 
node with simple queue hierarchy. Since Node doesn't have enough resources to 
accomodate, preemption happens because of node resource constraint.
+// Guaranteed and Max resource set on both victim queue path and preemptor 
queue path in 2 levels. victim and preemptor queue are siblings.
+// Parent is over guaranteed whereas preemptor is under guaranteed with 
pending pods. Parent is over guaranteed because of another child.
+// Setup:
+// Nodes are Node1 and Node2. Nodes are full. No space to accommodate the ask.
+// root.parent. Guaranteed set on parent, first: 2. Usage is first: 6. So over 
guaranteed.
+// root.parent.child1. No Guaranteed set. Usage is first: 6. 6 Allocations 
(belongs to single app) are running. Each Allocation usage is first:1. Total 
usage is first:6.
+// root.parent.child2. Guaranteed set, first: 1. Ask of first:1 is waiting for 
resources.
+// 1 Allocation on root.parent.child1 should be preempted to free up resources 
for ask arrived in root.parent.child2.
+func TestTryPreemptionOnNodeWithOGParentAndUGPreemptor(t *testing.T) {
+       node1 := newNode(nodeID1, map[string]resources.Quantity{"first": 3, 
"pods": 1})
+       node2 := newNode(nodeID2, map[string]resources.Quantity{"first": 3, 
"pods": 1})
+       iterator := getNodeIteratorFn(node1, node2)
+       rootQ, err := createRootQueue(nil)
+       assert.NilError(t, err)
+       parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true, 
map[string]string{"first": "20"}, map[string]string{"first": "2"})
+       assert.NilError(t, err)
+       childQ1, err := createManagedQueueGuaranteed(parentQ, "child1", false, 
nil, nil)
+       assert.NilError(t, err)
+       childQ2, err := createManagedQueueGuaranteed(parentQ, "child2", false, 
map[string]string{"first": "10"}, map[string]string{"first": "1"})
+       assert.NilError(t, err)
+       app1 := newApplication(appID1, "default", "root.parent.child1")
+       app1.SetQueue(childQ1)
+       childQ1.applications[appID1] = app1
+
+       for i := 1; i <= 6; i++ {
+               ask1 := newAllocationAsk("alloc"+strconv.Itoa(i), appID1, 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1}))
+               ask1.createTime = time.Now().Add(time.Duration(i*-1) * 
time.Minute)
+               assert.NilError(t, app1.AddAllocationAsk(ask1))
+               if i%2 == 0 {
+                       alloc1 := markAllocated(nodeID1, ask1)
+                       app1.AddAllocation(alloc1)
+                       assert.Check(t, node1.TryAddAllocation(alloc1), "node 
alloc1 failed")
+                       assert.NilError(t, 
childQ1.IncAllocatedResource(ask1.GetAllocatedResource(), false))
+               } else {
+                       alloc1 := markAllocated(nodeID2, ask1)
+                       app1.AddAllocation(alloc1)
+                       assert.Check(t, node2.TryAddAllocation(alloc1), "node 
alloc1 failed")
+                       assert.NilError(t, 
childQ1.IncAllocatedResource(ask1.GetAllocatedResource(), false))
+               }
+       }
+       app2 := newApplication(appID2, "default", "root.parent.child2")
+       app2.SetQueue(childQ2)
+       childQ2.applications[appID2] = app2
+       ask3 := newAllocationAsk("alloc7", appID2, 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1}))
+       assert.NilError(t, app2.AddAllocationAsk(ask3))
+       headRoom := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "pods": 
3})
+       preemptor := NewPreemptor(app2, headRoom, 30*time.Second, ask3, 
iterator(), false)
+
+       // register predicate handler
+       preemptions := []mock.Preemption{
+               mock.NewPreemption(true, "alloc7", nodeID2, []string{"alloc1"}, 
0, 0),
+       }
+       plugin := mock.NewPreemptionPredicatePlugin(nil, nil, preemptions)
+       plugins.RegisterSchedulerPlugin(plugin)
+       defer plugins.UnregisterSchedulerPlugins()
+
+       result, ok := preemptor.TryPreemption()
+       assert.Assert(t, result != nil, "no result")
+       assert.Assert(t, ok, "no victims found")
+       assert.Equal(t, "alloc7", result.Request.allocationKey, "wrong alloc")
+       assert.Equal(t, nodeID2, result.NodeID, "wrong node")
+       assert.Check(t, node2.GetAllocation("alloc1").IsPreempted(), "alloc1 
preempted")
+}
+
 // TestTryPreemptionOnQueue Test try preemption on queue with simple queue 
hierarchy. Since Node has enough resources to accomodate, preemption happens 
because of queue resource constraint.
 // Guaranteed and Max resource set on both victim queue path and preemptor 
queue path in 2 levels. victim and preemptor queue are siblings.
 // Request (Preemptor) resource type matches with all resource types of the 
victim. But Guaranteed set only on specific resource type. 2 Victims are 
available, but 1 should be preempted because further preemption would make 
usage go below the guaranteed quota
diff --git a/pkg/scheduler/objects/queue.go b/pkg/scheduler/objects/queue.go
index a905fdd1..35993238 100644
--- a/pkg/scheduler/objects/queue.go
+++ b/pkg/scheduler/objects/queue.go
@@ -1712,6 +1712,15 @@ func (sq *Queue) FindEligiblePreemptionVictims(queuePath 
string, ask *Allocation
                return nil
        }
 
+       // create snapshot for ask or preemptor queue
+       sq.createPreemptionSnapshot(results, queuePath)
+       c := sq
+       // set the ask queue for all queues in the ask queue hierarchy
+       for c.parent != nil {
+               results[c.QueuePath].AskQueue = results[queuePath]
+               c = c.parent
+       }
+
        // walk the subtree contained within the preemption fence and collect 
potential victims organized by nodeID
        fence.findEligiblePreemptionVictims(results, queuePath, ask, 
priorityMap, queuePriority, false)
 
@@ -1719,7 +1728,7 @@ func (sq *Queue) FindEligiblePreemptionVictims(queuePath 
string, ask *Allocation
 }
 
 // createPreemptionSnapshot is used to create a snapshot of the current 
queue's resource usage and potential preemption victims
-func (sq *Queue) createPreemptionSnapshot(cache 
map[string]*QueuePreemptionSnapshot) *QueuePreemptionSnapshot {
+func (sq *Queue) createPreemptionSnapshot(cache 
map[string]*QueuePreemptionSnapshot, askQueuePath string) 
*QueuePreemptionSnapshot {
        if sq == nil {
                return nil
        }
@@ -1729,7 +1738,7 @@ func (sq *Queue) createPreemptionSnapshot(cache 
map[string]*QueuePreemptionSnaps
                return snapshot
        }
 
-       parentSnapshot := sq.parent.createPreemptionSnapshot(cache)
+       parentSnapshot := sq.parent.createPreemptionSnapshot(cache, 
askQueuePath)
        sq.RLock()
        defer sq.RUnlock()
        snapshot = &QueuePreemptionSnapshot{
@@ -1741,6 +1750,7 @@ func (sq *Queue) createPreemptionSnapshot(cache 
map[string]*QueuePreemptionSnaps
                MaxResource:        sq.maxResource.Clone(),
                GuaranteedResource: sq.guaranteedResource.Clone(),
                PotentialVictims:   make([]*Allocation, 0),
+               AskQueue:           cache[askQueuePath],
        }
        cache[sq.QueuePath] = snapshot
        return snapshot
@@ -1750,20 +1760,13 @@ func (sq *Queue) findEligiblePreemptionVictims(results 
map[string]*QueuePreempti
        if sq == nil {
                return
        }
-
-       // if this is the target queue, return it but with an empty victim list 
so we can use it in calculations
-       if sq.QueuePath == queuePath {
-               sq.createPreemptionSnapshot(results)
-               return
-       }
-
        if sq.IsLeafQueue() {
                // leaf queue, skip queue if preemption is disabled
                if sq.GetPreemptionPolicy() == 
policies.DisabledPreemptionPolicy {
                        return
                }
 
-               victims := sq.createPreemptionSnapshot(results)
+               victims := sq.createPreemptionSnapshot(results, queuePath)
 
                // skip this queue if we are within guaranteed limits
                remaining := 
results[sq.QueuePath].GetRemainingGuaranteedResource()


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


Reply via email to