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 e18603a7 [YUNIKORN-2795] Handle preemption between two siblings
without causing preemption loop (#944)
e18603a7 is described below
commit e18603a7dd689b50e2975559887180cc8f3da08c
Author: Manikandan R <[email protected]>
AuthorDate: Thu Aug 29 13:14:30 2024 -0500
[YUNIKORN-2795] Handle preemption between two siblings without causing
preemption loop (#944)
Closes: #944
Signed-off-by: Craig Condit <[email protected]>
---
pkg/scheduler/objects/preemption.go | 13 ++
pkg/scheduler/objects/preemption_queue_test.go | 277 ++++++++++++++-----------
pkg/scheduler/objects/preemption_test.go | 219 +++++++++++++++++++
3 files changed, 383 insertions(+), 126 deletions(-)
diff --git a/pkg/scheduler/objects/preemption.go
b/pkg/scheduler/objects/preemption.go
index 3184658a..0f62163f 100644
--- a/pkg/scheduler/objects/preemption.go
+++ b/pkg/scheduler/objects/preemption.go
@@ -20,6 +20,7 @@ package objects
import (
"sort"
+ "strings"
"sync"
"time"
@@ -827,6 +828,18 @@ func (qps *QueuePreemptionSnapshot)
GetRemainingGuaranteedResource() *resources.
if qps.AskQueue.QueuePath == qps.QueuePath &&
!remainingGuaranteed.IsEmpty() {
return resources.MergeIfNotPresent(remainingGuaranteed,
parent)
}
+ // Queue (potential victim queue path) being processed
currently sharing common ancestors or parent with ask queue should not
propagate its
+ // actual remaining guaranteed to rest of queue's in the queue
hierarchy downwards to let them use their own remaining guaranteed only if
guaranteed
+ // has been set. Otherwise, propagating the remaining
guaranteed downwards would give wrong perception and those queues might not be
chosen
+ // as victims for sibling ( who is under guaranteed and
starving for resources) in the same level.
+ // Overall, this increases the chance of choosing victims for
preemptor from siblings without causing preemption storm or loop.
+ askQueueRemainingGuaranteed :=
qps.AskQueue.GuaranteedResource.Clone()
+ askQueueUsed := qps.AskQueue.AllocatedResource.Clone()
+ askQueueUsed = resources.SubOnlyExisting(askQueueUsed,
qps.AskQueue.PreemptingResource)
+ askQueueRemainingGuaranteed =
resources.SubOnlyExisting(askQueueRemainingGuaranteed, askQueueUsed)
+ if !remainingGuaranteed.IsEmpty() &&
strings.HasPrefix(qps.AskQueue.QueuePath, qps.QueuePath) &&
!askQueueRemainingGuaranteed.IsEmpty() {
+ return nil
+ }
}
return resources.ComponentWiseMin(remainingGuaranteed, parent)
}
diff --git a/pkg/scheduler/objects/preemption_queue_test.go
b/pkg/scheduler/objects/preemption_queue_test.go
index 90af333f..d2a287bd 100644
--- a/pkg/scheduler/objects/preemption_queue_test.go
+++ b/pkg/scheduler/objects/preemption_queue_test.go
@@ -24,6 +24,35 @@ import (
"github.com/apache/yunikorn-core/pkg/common/resources"
)
+var smallestRes =
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5})
+var childRes =
resources.NewResourceFromMap(map[string]resources.Quantity{"second": 5})
+var smallestResPlusChildRes = resources.Add(smallestRes, childRes)
+var smallestResDouble = resources.Multiply(smallestRes, 2)
+var smallestResDoublePlusChildRes =
resources.Add(resources.Multiply(smallestRes, 2), childRes)
+var smallestResMultiplyByZero =
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 0})
+var smallestResMultiplyByMinusOne = resources.Multiply(smallestRes, -1)
+var smallestResMultiplyByMinusOnePlusChildRes =
resources.Add(resources.Multiply(smallestRes, -1), childRes)
+var childResMultiplyByZero =
resources.NewResourceFromMap(map[string]resources.Quantity{"second": 0})
+var childResMultiplyByMinusOne = resources.Multiply(childRes, -1)
+var childResDouble = resources.Multiply(childRes, 2)
+var smallestResDoublePlusChildResDouble =
resources.Add(resources.Multiply(smallestRes, 2), childResDouble)
+var smallestResPlusChildResDouble = resources.Add(smallestRes, childResDouble)
+var smallestResMultiplyByZeroPluschildResMultiplyByZero =
resources.Add(smallestResMultiplyByZero, childResMultiplyByZero)
+var smallestResMultiplyByZeroPluschildResMultiplyByMinusOne =
resources.Add(smallestResMultiplyByZero, childResMultiplyByMinusOne)
+
+type resource struct {
+ root, parent, childQ1, childQ2 *resources.Resource
+}
+
+type assertMessage struct {
+ rootR, parentR, childQ1R, childQ2R string
+}
+
+type res struct {
+ guaranteed, allocated, remaining resource
+ assertMessages assertMessage
+}
+
func TestGetPreemptableResource(t *testing.T) {
// no guaranteed and no usage. so nothing to preempt
rootQ, err := createRootQueue(map[string]string{"first": "20"})
@@ -122,119 +151,113 @@ 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))
+ guaranteed1 := resource{resources.Multiply(smallestRes, 2),
smallestRes, childRes, smallestRes}
+ guaranteed2 := resource{resources.Multiply(smallestRes, 2),
smallestResPlusChildRes, childRes, smallestRes}
+ allocated1 := resource{nil, nil, nil, nil}
+ allocated2 := resource{smallestResDouble, smallestResDouble, nil,
smallestRes}
+ allocated3 := resource{smallestResDoublePlusChildRes,
smallestResPlusChildRes, childRes, smallestRes}
+ allocated4 := resource{smallestResDoublePlusChildResDouble,
smallestResPlusChildResDouble, childResDouble, smallestRes}
+ allocated5 := resource{smallestResDoublePlusChildResDouble,
smallestResPlusChildResDouble, childResDouble,
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2})}
+ assertMessage1 := "guaranteed set, but no usage. so all guaranteed
should be in remaining"
+ assertMessage2 := "guaranteed set, but no usage. so all guaranteed +
parent remaining guaranteed should be in remaining"
+ assertMessage3 := "guaranteed set, but no usage. so all its guaranteed
(because it is lesser than parent's guaranteed) should be in remaining"
+ assertMessage4 := "guaranteed set, used completely. so all guaranteed
should be in remaining"
+ assertMessage5 := "guaranteed set, used double than guaranteed. so
remaining should be in -ve"
+ assertMessage6 := "guaranteed set, but no usage. However remaining
should include its all guaranteed + parent remaining guaranteed"
+ assertMessage7 := "guaranteed set, used all guaranteed. remaining
should be based on ask queue"
+ assertMessage8 := "guaranteed set, used completely. usage also has
extra resource types. However, no remaining"
+ assertMessage9 := "guaranteed set, used completely. so, no remaining"
+ assertMessage10 := "guaranteed set, but no usage. Still, no remaining
in guaranteed because of its parent queue"
+ assertMessage11 := "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"
+ assertMessage12 := "guaranteed set, used bit lesser. parent's usage
also has extra resource types. remaining should be based on ask queue"
+
+ assertMessageStruct1 := assertMessage{assertMessage1, assertMessage1,
assertMessage1, assertMessage1}
+ assertMessageStruct2 := assertMessage{assertMessage1, assertMessage1,
assertMessage2, assertMessage3}
+ assertMessageStruct3 := assertMessage{assertMessage4, assertMessage5,
assertMessage6, assertMessage7}
+ assertMessageStruct4 := assertMessage{assertMessage8, assertMessage8,
assertMessage9, assertMessage10}
+ assertMessageStruct5 := assertMessage{assertMessage8, assertMessage8,
assertMessage5, assertMessage10}
+ assertMessageStruct6 := assertMessage{assertMessage8, assertMessage11,
assertMessage5, assertMessage12}
+
+ // 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
+ res1 := res{guaranteed: resource{resources.Multiply(smallestRes, 2),
nil, childRes, nil},
+ allocated: allocated1,
+ remaining: resource{smallestResDouble, smallestResDouble,
smallestResDoublePlusChildRes, smallestResDouble},
+ assertMessages: assertMessageStruct1,
+ }
var tests = []struct {
- testName string
- askQueue *Queue
- childQ2Remaining *resources.Resource
- childQ2Remaining1 *resources.Resource
+ testName string
+ askQueue *Queue
+ resources []res
}{
-
{"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))},
+ {testName:
"UnderGuaranteedChildQueue_Under_OverGuaranteedParentQueue_Does_Not_Have_Higher_Precedence_When_AskQueue_Is_Different_From_UnderGuaranteedChildQueue",
+ // ask queue diverged from root itself, not sharing any
common queue path with potential victim queue paths
+ askQueue: childQ3,
+ resources: []res{
+ res1,
+ // guaranteed set but no usage. so nothing to
preempt
+ // clean start for the snapshot: whole
hierarchy with guarantee
+ {guaranteed: guaranteed1, allocated:
allocated1, remaining: resource{smallestResDouble, smallestRes,
smallestResPlusChildRes, smallestRes}, assertMessages: assertMessageStruct2},
+ // clean start for the snapshot: all set
guaranteed
+ // add usage to parent + root + child2 : use
all guaranteed set
+ // child2 remaining behaviour changes based on
the ask queue. Its remaining is min permissive of its own values and parent or
ancestor values.
+ {guaranteed: guaranteed1, allocated:
allocated2, remaining: resource{smallestResMultiplyByZero,
smallestResMultiplyByMinusOne, smallestResMultiplyByMinusOnePlusChildRes,
smallestResMultiplyByMinusOne}, assertMessages: assertMessageStruct3},
+ // clean start for the snapshot: all set
guaranteed
+ // add usage for all: use exactly guaranteed at
parent and child1 level
+ // parent guarantee used for one type child
guarantee used for second type
+ {guaranteed: guaranteed1, allocated:
allocated3, remaining: resource{smallestResMultiplyByZero,
smallestResMultiplyByZero, smallestResMultiplyByZeroPluschildResMultiplyByZero,
smallestResMultiplyByZero}, assertMessages: assertMessageStruct4},
+ // clean start for the snapshot: all set
guaranteed
+ // add usage for root + parent: use exactly
guaranteed at parent and child2 level
+ // add usage to child1: use double than
guaranteed
+ // parent guarantee used for one type child
guarantee used for second type
+ {guaranteed: guaranteed1, allocated:
allocated4, remaining: resource{smallestResMultiplyByZero,
smallestResMultiplyByZero,
smallestResMultiplyByZeroPluschildResMultiplyByMinusOne,
smallestResMultiplyByZero}, assertMessages: assertMessageStruct5},
+ // 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. Its remaining is min permissive of its own values and parent or
ancestor values.
+ {guaranteed: guaranteed2, allocated:
allocated5, remaining: resource{smallestResMultiplyByZero,
smallestResMultiplyByZeroPluschildResMultiplyByMinusOne,
smallestResMultiplyByZeroPluschildResMultiplyByMinusOne,
smallestResMultiplyByZeroPluschildResMultiplyByMinusOne}, assertMessages:
assertMessageStruct6},
+ },
+ },
+ {testName:
"UnderGuaranteedChildQueue_Under_OverGuaranteedParentQueue_Has_Higher_Precedence_When_AskQueue_Is_Same_As_UnderGuaranteedChildQueue",
+ // ask queue shares common queue path with potential
victim queue paths
+ askQueue: childQ2,
+ resources: []res{
+ res1,
+ // guaranteed set but no usage. so nothing to
preempt
+ // clean start for the snapshot: whole
hierarchy with guarantee
+ {guaranteed: guaranteed1, allocated:
allocated1, remaining: resource{smallestResDouble, nil, childRes, smallestRes},
assertMessages: assertMessageStruct2},
+ // clean start for the snapshot: all set
guaranteed
+ // add usage to parent + root + 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.
+ {guaranteed: guaranteed1, allocated:
allocated2, remaining: resource{smallestResMultiplyByZero, nil, childRes,
smallestResMultiplyByZero}, assertMessages: assertMessageStruct3},
+ // clean start for the snapshot: all set
guaranteed
+ // add usage for all: use exactly guaranteed at
parent and child1 level
+ // parent guarantee used for one type child
guarantee used for second type
+ {guaranteed: guaranteed1, allocated:
allocated3, remaining: resource{smallestResMultiplyByZero, nil,
childResMultiplyByZero, smallestResMultiplyByZero}, assertMessages:
assertMessageStruct4},
+ // clean start for the snapshot: all set
guaranteed
+ // add usage for root + parent: use exactly
guaranteed at parent and child2 level
+ // add usage to child1: use double than
guaranteed
+ // parent guarantee used for one type child
guarantee used for second type
+ {guaranteed: guaranteed1, allocated:
allocated4, remaining: resource{smallestResMultiplyByZero, nil,
childResMultiplyByMinusOne, smallestResMultiplyByZero}, assertMessages:
assertMessageStruct5},
+ // 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. 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.
+ {guaranteed: guaranteed2, allocated:
allocated5, remaining: resource{smallestResMultiplyByZero, nil,
childResMultiplyByMinusOne,
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 3})},
assertMessages: assertMessageStruct6},
+ },
+ },
}
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")
+ t.Run(tt.testName, func(t *testing.T) {
+ for _, res1 := range tt.resources {
+ assertRemaining(t, rootQ, parentQ, childQ2,
childQ1, tt.askQueue, res1)
+ }
+ })
}
}
@@ -255,11 +278,26 @@ func setup(t *testing.T) (rootQ, parentQ, childQ1,
childQ2, childQ3 *Queue) {
return
}
-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")
+func assertRemaining(t *testing.T, rootQ *Queue, parentQ *Queue, childQ2
*Queue, childQ1 *Queue, askQueue *Queue, res1 res) {
+ var rootRemaining, pRemaining, cRemaining1, cRemaining2
*resources.Resource
+ resetQueueResources(rootQ, parentQ, childQ1, childQ2)
+ rootQ.guaranteedResource = res1.guaranteed.root
+ parentQ.guaranteedResource = res1.guaranteed.parent
+ childQ1.guaranteedResource = res1.guaranteed.childQ1
+ childQ2.guaranteedResource = res1.guaranteed.childQ2
+ rootQ.allocatedResource = res1.allocated.root
+ parentQ.allocatedResource = res1.allocated.parent
+ childQ1.allocatedResource = res1.allocated.childQ1
+ childQ2.allocatedResource = res1.allocated.childQ2
+ qpsRoot, qpsParent, qpsChild1, qpsChild2 := createQPSCache(rootQ,
parentQ, childQ1, childQ2, askQueue)
+ rootRemaining = qpsRoot.GetRemainingGuaranteedResource()
+ pRemaining = qpsParent.GetRemainingGuaranteedResource()
+ cRemaining1 = qpsChild1.GetRemainingGuaranteedResource()
+ cRemaining2 = qpsChild2.GetRemainingGuaranteedResource()
+ assert.Assert(t, resources.DeepEquals(rootRemaining,
res1.remaining.root), res1.assertMessages.rootR)
+ assert.Assert(t, resources.DeepEquals(pRemaining,
res1.remaining.parent), res1.assertMessages.parentR)
+ assert.Assert(t, resources.DeepEquals(cRemaining1,
res1.remaining.childQ1), res1.assertMessages.childQ1R)
+ assert.Assert(t, resources.DeepEquals(cRemaining2,
res1.remaining.childQ2), res1.assertMessages.childQ2R)
}
func resetQueueResources(rootQ *Queue, parentQ *Queue, childQ1 *Queue, childQ2
*Queue) {
@@ -297,19 +335,6 @@ func createQPSCache(rootQ *Queue, parentQ *Queue, childQ1
*Queue, childQ2 *Queue
return qpsRoot, qpsParent, qpsChild1, qpsChild2
}
-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()
- cRemaining2 := qpsChild2.GetRemainingGuaranteedResource()
- return rootRemaining, pRemaining, cRemaining1, cRemaining2
-}
-
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, nil)
rootRemaining := qpsRoot.GetPreemptableResource()
diff --git a/pkg/scheduler/objects/preemption_test.go
b/pkg/scheduler/objects/preemption_test.go
index 07574387..5732a0b1 100644
--- a/pkg/scheduler/objects/preemption_test.go
+++ b/pkg/scheduler/objects/preemption_test.go
@@ -1572,6 +1572,225 @@ func
TestTryPreemption_OnNode_AskResTypesSame_GuaranteedSetOnVictimAndPreemptorS
assert.Check(t, alloc3.IsPreempted(), "alloc3 not preempted")
}
+//
TestTryPreemption_OnNode_UGParent_With_UGPreemptorChild_GNotSetOnVictimChild_As_Siblings
Test try preemption with 2 level queue hierarchy. Since Node doesn't have
enough resources to accomodate, preemption happens because of node resource
constraint.
+// Under guaranteed parent with 2 child queues. Victim Child1 queue doesn't
have guaranteed set. Under guaranteed Preemptor Child2 queue is starving for
resources.
+// Ask (Preemptor) resource type matches with the victim's resource type.
Needs to be preempted because matching resource type has been configured as
guaranteed.
+// Setup:
+// Nodes are Node1. Node is full, doesn't enough space to accommodate the ask.
+// root.parent.parent1.child1. Guaranteed not set directly, but set on
root.parent.parent1, vcores: 10. 2 Allocations (belongs to two diff apps) are
running. Each Allocation usage is vcores:1. Total usage is vcores:2.
+// root.parent.parent1.child2. Guaranteed set, vcores: 1. Ask of vcores: 1 is
waiting for resources.
+// root.parent.parent2.child3. No usage, no guaranteed set
+// 1 Allocation on root.parent.parent1.child1 should be preempted to free up
resources for ask arrived in root.parent.parent1.child2. Also, it won't lead to
preemption storm or loop.
+func
TestTryPreemption_OnNode_UGParent_With_UGPreemptorChild_GNotSetOnVictimChild_As_Siblings(t
*testing.T) {
+ node := newNode(nodeID1, map[string]resources.Quantity{"vcores": 2})
+ iterator := getNodeIteratorFn(node)
+ rootQ, err := createRootQueue(map[string]string{"vcores": "25"})
+ assert.NilError(t, err)
+ parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true,
map[string]string{"vcores": "20"}, nil)
+ assert.NilError(t, err)
+ parentQ1, err := createManagedQueueGuaranteed(parentQ, "parent1", true,
nil, map[string]string{"vcores": "10"})
+ assert.NilError(t, err)
+ parentQ2, err := createManagedQueueGuaranteed(parentQ, "parent2", true,
nil, nil)
+ assert.NilError(t, err)
+
+ childQ1, err := createManagedQueueGuaranteed(parentQ1, "child1", false,
nil, nil)
+ assert.NilError(t, err)
+ childQ2, err := createManagedQueueGuaranteed(parentQ1, "child2", false,
nil, map[string]string{"vcores": "1"})
+ assert.NilError(t, err)
+ _, err = createManagedQueueGuaranteed(parentQ2, "child3", false, nil,
nil)
+ assert.NilError(t, err)
+
+ app1 := newApplication(appID1, "default", "root.parent.parent1.child1")
+ app1.SetQueue(childQ1)
+ childQ1.applications[appID1] = app1
+ ask1 := newAllocationAsk("alloc1", appID1,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ ask1.createTime = time.Now().Add(-2 * time.Minute)
+ assert.NilError(t, app1.AddAllocationAsk(ask1))
+ alloc1 := markAllocated(nodeID1, ask1)
+ app1.AddAllocation(alloc1)
+ assert.Check(t, node.TryAddAllocation(alloc1), "node alloc1 failed")
+ assert.NilError(t,
childQ1.IncAllocatedResource(ask1.GetAllocatedResource(), false))
+
+ app2 := newApplication(appID2, "default", "root.parent.parent1.child1")
+ app2.SetQueue(childQ2)
+ childQ1.applications[appID2] = app2
+ ask2 := newAllocationAsk("alloc2", appID2,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ ask2.createTime = time.Now().Add(-1 * time.Minute)
+ assert.NilError(t, app2.AddAllocationAsk(ask2))
+ alloc2 := markAllocated(nodeID1, ask2)
+ app2.AddAllocation(alloc2)
+ assert.Check(t, node.TryAddAllocation(alloc2), "node alloc2 failed")
+ assert.NilError(t,
childQ1.IncAllocatedResource(ask2.GetAllocatedResource(), false))
+
+ app3 := newApplication(appID3, "default", "root.parent.parent1.child2")
+ app3.SetQueue(childQ2)
+ childQ2.applications[appID3] = app3
+ ask3 := newAllocationAsk("alloc3", appID3,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ assert.NilError(t, app3.AddAllocationAsk(ask3))
+
+ headRoom :=
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 2})
+ preemptor := NewPreemptor(app3, headRoom, 30*time.Second, ask3,
iterator(), false)
+
+ // register predicate handler
+ preemptions := []mock.Preemption{mock.NewPreemption(true, "alloc3",
nodeID1, []string{"alloc2"}, 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, "alloc3", result.Request.GetAllocationKey(), "wrong
alloc")
+ assert.Equal(t, nodeID1, result.NodeID, "wrong node")
+ assert.Equal(t, nodeID1, alloc2.nodeID, "wrong node")
+ assert.Check(t, !alloc1.IsPreempted(), "alloc1 preempted")
+ assert.Check(t, alloc2.IsPreempted(), "alloc2 not preempted")
+}
+
+// TestTryPreemption_OnNode_UGParent_With_GNotSetOnBothChilds Test try
preemption with 2 level queue hierarchy. Since Node doesn't have enough
resources to accomodate, preemption happens because of node resource constraint.
+// Under guaranteed parent with 2 child queues. Victim Child1 queue doesn't
have guaranteed set. Preemptor Child2 queue doesn't have guaranteed set.
+// Ask (Preemptor) resource type matches with the victim's resource type.
Needs to be preempted because matching resource type has been configured as
guaranteed.
+// Setup:
+// Nodes are Node1. Node is full, doesn't enough space to accommodate the ask.
+// root.parent.parent1.child1. Guaranteed not set directly, but set on
root.parent.parent1, vcores: 10. 2 Allocations (belongs to two diff apps) are
running. Each Allocation usage is vcores:1. Total usage is vcores:2.
+// root.parent.parent1.child2. Guaranteed not set directly, but set on
root.parent.parent1, vcores: 10. Ask of vcores: 1 is waiting for resources.
+// root.parent.parent2.child3. No usage, no guaranteed set
+// 1 Allocation on root.parent.parent1.child1 should not be preempted to free
up resources for ask arrived in root.parent.parent1.child2 because it could
lead to preemption storm or loop.
+func TestTryPreemption_OnNode_UGParent_With_GNotSetOnBothChilds(t *testing.T) {
+ node := newNode(nodeID1, map[string]resources.Quantity{"vcores": 2})
+ iterator := getNodeIteratorFn(node)
+ rootQ, err := createRootQueue(map[string]string{"vcores": "25"})
+ assert.NilError(t, err)
+ parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true,
map[string]string{"vcores": "20"}, nil)
+ assert.NilError(t, err)
+ parentQ1, err := createManagedQueueGuaranteed(parentQ, "parent1", true,
nil, map[string]string{"vcores": "10"})
+ assert.NilError(t, err)
+ parentQ2, err := createManagedQueueGuaranteed(parentQ, "parent2", true,
nil, nil)
+ assert.NilError(t, err)
+
+ childQ1, err := createManagedQueueGuaranteed(parentQ1, "child1", false,
nil, nil)
+ assert.NilError(t, err)
+ childQ2, err := createManagedQueueGuaranteed(parentQ1, "child2", false,
nil, nil)
+ assert.NilError(t, err)
+ _, err = createManagedQueueGuaranteed(parentQ2, "child3", false, nil,
nil)
+ assert.NilError(t, err)
+
+ app1 := newApplication(appID1, "default", "root.parent.parent1.child1")
+ app1.SetQueue(childQ1)
+ childQ1.applications[appID1] = app1
+ ask1 := newAllocationAsk("alloc1", appID1,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ ask1.createTime = time.Now().Add(-2 * time.Minute)
+ assert.NilError(t, app1.AddAllocationAsk(ask1))
+ alloc1 := markAllocated(nodeID1, ask1)
+ app1.AddAllocation(alloc1)
+ assert.Check(t, node.TryAddAllocation(alloc1), "node alloc1 failed")
+ assert.NilError(t,
childQ1.IncAllocatedResource(ask1.GetAllocatedResource(), false))
+
+ app2 := newApplication(appID2, "default", "root.parent.parent1.child1")
+ app2.SetQueue(childQ2)
+ childQ1.applications[appID2] = app2
+ ask2 := newAllocationAsk("alloc2", appID2,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ ask2.createTime = time.Now().Add(-1 * time.Minute)
+ assert.NilError(t, app2.AddAllocationAsk(ask2))
+ alloc2 := markAllocated(nodeID1, ask2)
+ app2.AddAllocation(alloc2)
+ assert.Check(t, node.TryAddAllocation(alloc2), "node alloc2 failed")
+ assert.NilError(t,
childQ1.IncAllocatedResource(ask2.GetAllocatedResource(), false))
+
+ app3 := newApplication(appID3, "default", "root.parent.parent1.child2")
+ app3.SetQueue(childQ2)
+ childQ2.applications[appID3] = app3
+ ask3 := newAllocationAsk("alloc3", appID3,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ assert.NilError(t, app3.AddAllocationAsk(ask3))
+
+ headRoom :=
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 2})
+ preemptor := NewPreemptor(app3, headRoom, 30*time.Second, ask3,
iterator(), false)
+
+ // register predicate handler
+ plugin := mock.NewPreemptionPredicatePlugin(nil, nil, nil)
+ plugins.RegisterSchedulerPlugin(plugin)
+ defer plugins.UnregisterSchedulerPlugins()
+
+ result, ok := preemptor.TryPreemption()
+ assert.Assert(t, result == nil, "preemption is yielding results")
+ assert.Assert(t, !ok, "victims found")
+}
+
+//
TestTryPreemption_OnNode_UGParent_With_UGPreemptorChild_OGVictimChild_As_Siblings
Test try preemption with 2 level queue hierarchy. Since Node doesn't have
enough resources to accomodate, preemption happens because of node resource
constraint.
+// Under guaranteed parent with 2 child queues. Over Guaranteed Victim Child1
queue has guaranteed set. Under Guaranteed Preemptor Child2 queue is starving
for resources.
+// Ask (Preemptor) resource type matches with the victim's resource type.
Needs to be preempted because matching resource type has been configured as
guaranteed.
+// Setup:
+// Nodes are Node1. Node is full, doesn't enough space to accommodate the ask.
+// root.parent.parent1.child1. Guaranteed of vcores:1 set directly , but also
set on root.parent.parent1, vcores: 10. 2 Allocations (belongs to two diff
apps) are running. Each Allocation usage is vcores:1. Total usage is vcores:2.
+// root.parent.parent1.child2. Guaranteed of vcores:1 set directly, but also
set on root.parent.parent1, vcores: 10. Ask of vcores: 1 is waiting for
resources.
+// root.parent.parent2.child3. No usage, no guaranteed set
+// 1 Allocation on root.parent.parent1.child1 should be preempted to free up
resources for ask arrived in root.parent.parent1.child2. Also, it won't lead to
preemption storm or loop.
+func
TestTryPreemption_OnNode_UGParent_With_UGPreemptorChild_OGVictimChild_As_Siblings(t
*testing.T) {
+ node := newNode(nodeID1, map[string]resources.Quantity{"vcores": 2})
+ iterator := getNodeIteratorFn(node)
+ rootQ, err := createRootQueue(map[string]string{"vcores": "25"})
+ assert.NilError(t, err)
+ parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true,
map[string]string{"vcores": "20"}, nil)
+ assert.NilError(t, err)
+ parentQ1, err := createManagedQueueGuaranteed(parentQ, "parent1", true,
nil, map[string]string{"vcores": "10"})
+ assert.NilError(t, err)
+ parentQ2, err := createManagedQueueGuaranteed(parentQ, "parent2", true,
nil, nil)
+ assert.NilError(t, err)
+
+ childQ1, err := createManagedQueueGuaranteed(parentQ1, "child1", false,
nil, map[string]string{"vcores": "1"})
+ assert.NilError(t, err)
+ childQ2, err := createManagedQueueGuaranteed(parentQ1, "child2", false,
nil, map[string]string{"vcores": "1"})
+ assert.NilError(t, err)
+ _, err = createManagedQueueGuaranteed(parentQ2, "child3", false, nil,
nil)
+ assert.NilError(t, err)
+
+ app1 := newApplication(appID1, "default", "root.parent.parent1.child1")
+ app1.SetQueue(childQ1)
+ childQ1.applications[appID1] = app1
+ ask1 := newAllocationAsk("alloc1", appID1,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ ask1.createTime = time.Now().Add(-2 * time.Minute)
+ assert.NilError(t, app1.AddAllocationAsk(ask1))
+ alloc1 := markAllocated(nodeID1, ask1)
+ app1.AddAllocation(alloc1)
+ assert.Check(t, node.TryAddAllocation(alloc1), "node alloc1 failed")
+ assert.NilError(t,
childQ1.IncAllocatedResource(ask1.GetAllocatedResource(), false))
+
+ app2 := newApplication(appID2, "default", "root.parent.parent1.child1")
+ app2.SetQueue(childQ2)
+ childQ1.applications[appID2] = app2
+ ask2 := newAllocationAsk("alloc2", appID2,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ ask2.createTime = time.Now().Add(-1 * time.Minute)
+ assert.NilError(t, app2.AddAllocationAsk(ask2))
+ alloc2 := markAllocated(nodeID1, ask2)
+ app2.AddAllocation(alloc2)
+ assert.Check(t, node.TryAddAllocation(alloc2), "node alloc2 failed")
+ assert.NilError(t,
childQ1.IncAllocatedResource(ask2.GetAllocatedResource(), false))
+
+ app3 := newApplication(appID3, "default", "root.parent.parent1.child2")
+ app3.SetQueue(childQ2)
+ childQ2.applications[appID3] = app3
+ ask3 := newAllocationAsk("alloc3", appID3,
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 1}))
+ assert.NilError(t, app3.AddAllocationAsk(ask3))
+
+ headRoom :=
resources.NewResourceFromMap(map[string]resources.Quantity{"vcores": 2})
+ preemptor := NewPreemptor(app3, headRoom, 30*time.Second, ask3,
iterator(), false)
+
+ // register predicate handler
+ preemptions := []mock.Preemption{mock.NewPreemption(true, "alloc3",
nodeID1, []string{"alloc2"}, 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, "alloc3", result.Request.GetAllocationKey(), "wrong
alloc")
+ assert.Equal(t, nodeID1, result.NodeID, "wrong node")
+ assert.Equal(t, nodeID1, alloc2.nodeID, "wrong node")
+ assert.Check(t, !alloc1.IsPreempted(), "alloc1 preempted")
+ assert.Check(t, alloc2.IsPreempted(), "alloc2 not preempted")
+}
+
func TestSolutionScoring(t *testing.T) {
singleAlloc := scoreMap(nodeID1, []bool{false}, []bool{true})
singleOriginator := scoreMap(nodeID1, []bool{true}, []bool{true})
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]