Skip to content

Commit 65b114f

Browse files
committed
[YUNIKORN-3228] Assess the impact of restart on scheduled preemption and propose a solution (#1068)
Allow existing allocations coming out of recovery process, VPA for existing running pods and ask transitioned into allocations as usual even if this put up the usage over the queue max resources. Either already scheduled quota preemption or configured quota preemption but completed would bring down the usage once the delay expires. Closes: #1068 Signed-off-by: mani <manirajv06@gmail.com>
1 parent 21874e5 commit 65b114f

11 files changed

Lines changed: 726 additions & 732 deletions

pkg/scheduler/objects/application.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ func (sa *Application) AddAllocationAsk(ask *Allocation) error {
685685

686686
// UpdateAllocationResources updates the app, queue, and user tracker with deltas for an allocation.
687687
// If an existing allocation cannot be found or alloc is invalid, an error is returned.
688-
func (sa *Application) UpdateAllocationResources(alloc *Allocation) error {
688+
func (sa *Application) UpdateAllocationResources(alloc *Allocation, isQuotaPreemptionEnabled bool) error {
689689
sa.Lock()
690690
defer sa.Unlock()
691691
if alloc == nil {
@@ -711,7 +711,7 @@ func (sa *Application) UpdateAllocationResources(alloc *Allocation) error {
711711
// update allocated resources
712712
sa.allocatedResource = resources.Add(sa.allocatedResource, delta)
713713
sa.allocatedResource.Prune()
714-
sa.queue.IncAllocatedResource(delta)
714+
sa.queue.IncAllocatedResource(delta, isQuotaPreemptionEnabled)
715715

716716
// update user usage
717717
sa.incUserResourceUsage(delta)

pkg/scheduler/objects/application_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,22 +1054,22 @@ func TestUpdateAllocationResourcePending(t *testing.T) {
10541054
assert.Check(t, resources.Equals(res, queue.GetPendingResource()), "resources not on queue")
10551055

10561056
// check nil alloc update
1057-
err = app.UpdateAllocationResources(nil)
1057+
err = app.UpdateAllocationResources(nil, false)
10581058
assert.Check(t, err != nil, "error not returned on nil alloc")
10591059

10601060
// check zero alloc update
10611061
zero := newAllocationWithKey(alloc, appID1, nodeID1, resources.NewResource())
1062-
err = app.UpdateAllocationResources(zero)
1062+
err = app.UpdateAllocationResources(zero, false)
10631063
assert.Check(t, err != nil, "error not returned on zero alloc")
10641064

10651065
// check missing alloc update
10661066
missing := newAllocationWithKey("missing", appID1, nodeID1, res)
1067-
err = app.UpdateAllocationResources(missing)
1067+
err = app.UpdateAllocationResources(missing, false)
10681068
assert.Check(t, err != nil, "error not returned on missing alloc")
10691069

10701070
// check zero delta
10711071
same := newAllocationWithKey(alloc, appID1, nodeID1, res)
1072-
err = app.UpdateAllocationResources(same)
1072+
err = app.UpdateAllocationResources(same, false)
10731073
assert.NilError(t, err, "error returned on same alloc size")
10741074
assert.Check(t, resources.Equals(res, app.GetPendingResource()), "resources not on app")
10751075
assert.Check(t, resources.Equals(res, queue.GetPendingResource()), "resources not on queue")
@@ -1078,7 +1078,7 @@ func TestUpdateAllocationResourcePending(t *testing.T) {
10781078
res2, err := resources.NewResourceFromConf(map[string]string{"first": "3"})
10791079
assert.NilError(t, err, "failed to create resource with error")
10801080
inc := newAllocationWithKey(alloc, appID1, nodeID1, res2)
1081-
err = app.UpdateAllocationResources(inc)
1081+
err = app.UpdateAllocationResources(inc, false)
10821082
assert.NilError(t, err, "error returned on incremented alloc")
10831083
assert.Check(t, resources.Equals(res2, app.GetPendingResource()), "resources not updated on app")
10841084
assert.Check(t, resources.Equals(res2, queue.GetPendingResource()), "resources not updated on queue")
@@ -1096,30 +1096,30 @@ func TestUpdateAllocationResourceAllocated(t *testing.T) {
10961096
res, err := resources.NewResourceFromConf(map[string]string{"first": "2"})
10971097
assert.NilError(t, err, "failed to create resource with error")
10981098
alloc1 := newAllocationWithKey(alloc, appID1, nodeID1, res)
1099-
queue.IncAllocatedResource(res)
1099+
queue.IncAllocatedResource(res, false)
11001100
app.RecoverAllocationAsk(alloc1)
11011101
app.AddAllocation(alloc1)
11021102
assert.Check(t, resources.Equals(res, queue.GetAllocatedResource()), "resources not on queue")
11031103

11041104
// check nil alloc update
1105-
err = app.UpdateAllocationResources(nil)
1105+
err = app.UpdateAllocationResources(nil, false)
11061106
assert.Check(t, err != nil, "error not returned on nil alloc")
11071107

11081108
// check zero alloc update
11091109
zero := newAllocationWithKey(alloc, appID1, nodeID1, resources.NewResource())
1110-
err = app.UpdateAllocationResources(zero)
1110+
err = app.UpdateAllocationResources(zero, false)
11111111
assert.Check(t, err != nil, "error not returned on zero alloc")
11121112

11131113
// check missing alloc update
11141114
missing := newAllocationWithKey("missing", appID1, nodeID1, res)
1115-
err = app.UpdateAllocationResources(missing)
1115+
err = app.UpdateAllocationResources(missing, false)
11161116
assert.Check(t, err != nil, "error not returned on missing alloc")
11171117
assert.Check(t, resources.Equals(res, app.GetAllocatedResource()), "resources not on app")
11181118
assert.Check(t, resources.Equals(res, queue.GetAllocatedResource()), "resources not on queue")
11191119

11201120
// check zero delta
11211121
same := newAllocationWithKey(alloc, appID1, nodeID1, res)
1122-
err = app.UpdateAllocationResources(same)
1122+
err = app.UpdateAllocationResources(same, false)
11231123
assert.NilError(t, err, "error returned on same alloc size")
11241124
assert.Check(t, resources.Equals(res, app.GetAllocatedResource()), "resources not on app")
11251125
assert.Check(t, resources.Equals(res, queue.GetAllocatedResource()), "resources not on queue")
@@ -1128,7 +1128,7 @@ func TestUpdateAllocationResourceAllocated(t *testing.T) {
11281128
res2, err := resources.NewResourceFromConf(map[string]string{"first": "3"})
11291129
assert.NilError(t, err, "failed to create resource with error")
11301130
inc := newAllocationWithKey(alloc, appID1, nodeID1, res2)
1131-
err = app.UpdateAllocationResources(inc)
1131+
err = app.UpdateAllocationResources(inc, false)
11321132
assert.NilError(t, err, "error returned on incremented alloc")
11331133
assert.Check(t, resources.Equals(res2, app.GetAllocatedResource()), "resources not updated on app")
11341134
assert.Check(t, resources.Equals(res2, queue.GetAllocatedResource()), "resources not updated on queue")

pkg/scheduler/objects/preemption_utilities_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func assignAllocationsToQueue(allocations []*Allocation, queue *Queue) {
149149
app = queue.applications[allocation.applicationID]
150150
}
151151
app.AddAllocation(allocation)
152-
queue.IncAllocatedResource(allocation.GetAllocatedResource())
152+
queue.IncAllocatedResource(allocation.GetAllocatedResource(), false)
153153
}
154154
}
155155

pkg/scheduler/objects/queue.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,15 @@ func (sq *Queue) setPreemptionTime(oldMaxResource *resources.Resource, oldDelay
496496
}
497497
}
498498

499+
// ResetPreemptionTime reset the quota preemption variables
500+
// Only for testing
501+
func (sq *Queue) ResetPreemptionTime() {
502+
sq.Lock()
503+
defer sq.Unlock()
504+
sq.quotaPreemptionStartTime = time.Time{}
505+
sq.quotaPreemptionDelay = 0
506+
}
507+
499508
// shouldTriggerPreemption returns true if quota preemption should be triggered based on the settings and the
500509
// current time.
501510
func (sq *Queue) shouldTriggerPreemption() bool {
@@ -1259,20 +1268,39 @@ func (sq *Queue) TryIncAllocatedResource(alloc *resources.Resource) error {
12591268
}
12601269

12611270
// IncAllocatedResource increments the allocated resources for this queue (recursively). No queue limits are checked.
1262-
func (sq *Queue) IncAllocatedResource(alloc *resources.Resource) {
1271+
// In case any quota preemption already scheduled or any quota preemption delay is configured, set the same delay again
1272+
// so that any usage overflow over the max quota caused by alloc would be brought down when the delay expires.
1273+
func (sq *Queue) IncAllocatedResource(alloc *resources.Resource, isQuotaPreemptionEnabled bool) {
12631274
// fall through if nil
12641275
if sq == nil {
12651276
return
12661277
}
12671278

12681279
// update parent
1269-
sq.parent.IncAllocatedResource(alloc)
1280+
sq.parent.IncAllocatedResource(alloc, isQuotaPreemptionEnabled)
12701281

12711282
// update this queue
12721283
sq.Lock()
12731284
defer sq.Unlock()
12741285
sq.allocatedResource = resources.Add(sq.allocatedResource, alloc)
12751286
sq.updateAllocatedResourceMetrics()
1287+
1288+
// Should apply quota preemption based on the config?
1289+
if !isQuotaPreemptionEnabled ||
1290+
!sq.quotaPreemptionStartTime.IsZero() ||
1291+
!sq.isManaged ||
1292+
sq.quotaPreemptionDelay == 0 ||
1293+
resources.IsZero(sq.maxResource) ||
1294+
sq.maxResource.StrictlyGreaterThanOrEqualsOnlyExisting(sq.allocatedResource) {
1295+
return
1296+
}
1297+
// Override the earlier set quota preemption time with the configured delay.
1298+
// Delay clock ticking from now.
1299+
sq.quotaPreemptionStartTime = time.Now().Add(sq.quotaPreemptionDelay)
1300+
log.Log(log.SchedQueue).Info("Overridden quota preemption time",
1301+
zap.String("queue", sq.QueuePath),
1302+
zap.Duration("delay", sq.quotaPreemptionDelay),
1303+
zap.Time("quotaPreemptionStartTime", sq.quotaPreemptionStartTime))
12761304
}
12771305

12781306
// allocatedResFits adds the passed in resource to the allocatedResource of the queue and checks if it still fits in the

pkg/scheduler/objects/queue_test.go

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -668,8 +668,8 @@ func TestHeadroom(t *testing.T) {
668668
var res *resources.Resource
669669
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "3"})
670670
assert.NilError(t, err, "failed to create resource")
671-
leaf1.IncAllocatedResource(res)
672-
leaf2.IncAllocatedResource(res)
671+
leaf1.IncAllocatedResource(res, false)
672+
leaf2.IncAllocatedResource(res, false)
673673

674674
// headRoom root should be this (max 20-10 - alloc 10-6)
675675
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "4"})
@@ -726,10 +726,10 @@ func TestHeadroomMerge(t *testing.T) {
726726
var res *resources.Resource
727727
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "5", "third": "5"})
728728
assert.NilError(t, err, "failed to create resource")
729-
leaf1.IncAllocatedResource(res)
729+
leaf1.IncAllocatedResource(res, false)
730730
res, err = resources.NewResourceFromConf(map[string]string{"third": "5", "fourth": "5"})
731731
assert.NilError(t, err, "failed to create resource")
732-
leaf2.IncAllocatedResource(res)
732+
leaf2.IncAllocatedResource(res, false)
733733

734734
// root headroom should be nil
735735
headRoom := root.getHeadRoom()
@@ -796,8 +796,8 @@ func TestMaxHeadroomNoMax(t *testing.T) {
796796
var res *resources.Resource
797797
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "3"})
798798
assert.NilError(t, err, "failed to create resource")
799-
leaf1.IncAllocatedResource(res)
800-
leaf2.IncAllocatedResource(res)
799+
leaf1.IncAllocatedResource(res, false)
800+
leaf2.IncAllocatedResource(res, false)
801801

802802
headRoom = root.getMaxHeadRoom()
803803
assert.Assert(t, headRoom == nil, "headRoom of root should be nil because no max set for all queues")
@@ -834,8 +834,8 @@ func TestMaxHeadroomMax(t *testing.T) {
834834
var res *resources.Resource
835835
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "3"})
836836
assert.NilError(t, err, "failed to create resource")
837-
leaf1.IncAllocatedResource(res)
838-
leaf2.IncAllocatedResource(res)
837+
leaf1.IncAllocatedResource(res, false)
838+
leaf2.IncAllocatedResource(res, false)
839839

840840
// root headroom should be nil
841841
headRoom := root.getMaxHeadRoom()
@@ -1475,7 +1475,7 @@ func TestOutStandingRequestMultipleChildrenWithMax(t *testing.T) {
14751475
leaf1App := newApplication("app-leaf1", "default", "root.parent.leaf1")
14761476
leaf1App.SetQueue(leaf1)
14771477
leaf1.AddApplication(leaf1App)
1478-
leaf1.IncAllocatedResource(allocatedRes)
1478+
leaf1.IncAllocatedResource(allocatedRes, false)
14791479
// use priority = 1000 for this ask to force ordering of queues when sorting
14801480
askLeaf1 := newAllocationAskAll("ask-leaf1", "app-leaf1", "", askRes, false, 1000)
14811481
askLeaf1.SetSchedulingAttempted(true)
@@ -1493,7 +1493,7 @@ func TestOutStandingRequestMultipleChildrenWithMax(t *testing.T) {
14931493
err = leaf2App.AddAllocationAsk(ask2Leaf2)
14941494
assert.NilError(t, err, "could not add ask")
14951495
leaf2.AddApplication(leaf2App)
1496-
leaf2.IncAllocatedResource(allocatedRes)
1496+
leaf2.IncAllocatedResource(allocatedRes, false)
14971497

14981498
outstanding := root.GetOutstandingRequests()
14991499
assert.Equal(t, 2, len(outstanding), "expected 2 outstanding requests to be collected")
@@ -2374,59 +2374,60 @@ func TestQuotaPreemptionSettings(t *testing.T) {
23742374
assert.NilError(t, err, "failed to create basic queue: %v", err)
23752375

23762376
testCases := []struct {
2377-
name string
2378-
maxRes map[string]string
2379-
conf configs.QueueConfig
2380-
oldDelay time.Duration
2381-
timeChange bool
2377+
name string
2378+
maxRes map[string]string
2379+
conf configs.QueueConfig
2380+
oldDelay time.Duration
2381+
timeChange bool
2382+
shouldApplyQuotaPreemption bool
23822383
}{
23832384
{"clearing max",
23842385
map[string]string{"memory": "500"},
23852386
configs.QueueConfig{
23862387
Resources: configs.Resources{
23872388
Max: nil,
23882389
},
2389-
}, 0, false},
2390+
}, 0, false, false},
23902391
{"clearing max with delay",
23912392
map[string]string{"memory": "500"},
23922393
configs.QueueConfig{
23932394
Resources: configs.Resources{
23942395
Max: nil,
23952396
},
23962397
Properties: map[string]string{configs.QuotaPreemptionDelay: "50ms"},
2397-
}, 0, false},
2398+
}, 0, false, false},
23982399
{"incorrect delay",
23992400
map[string]string{"memory": "500"},
24002401
configs.QueueConfig{
24012402
Resources: configs.Resources{
24022403
Max: nil,
24032404
},
24042405
Properties: map[string]string{configs.QuotaPreemptionDelay: "-50s"},
2405-
}, 0, false},
2406+
}, 0, false, false},
24062407
{"increase max with delay",
24072408
map[string]string{"memory": "500"},
24082409
configs.QueueConfig{
24092410
Resources: configs.Resources{
24102411
Max: map[string]string{"memory": "1000"},
24112412
},
24122413
Properties: map[string]string{configs.QuotaPreemptionDelay: "50ms"},
2413-
}, 50 * time.Millisecond, false},
2414+
}, 50 * time.Millisecond, false, false},
24142415
{"decrease max with delay",
24152416
map[string]string{"memory": "500"},
24162417
configs.QueueConfig{
24172418
Resources: configs.Resources{
24182419
Max: map[string]string{"memory": "100"},
24192420
},
24202421
Properties: map[string]string{configs.QuotaPreemptionDelay: "50ms"},
2421-
}, 50 * time.Millisecond, true},
2422+
}, 50 * time.Millisecond, true, true},
24222423
{"delay changed from 0 no max change",
24232424
map[string]string{"memory": "500"},
24242425
configs.QueueConfig{
24252426
Resources: configs.Resources{
24262427
Max: map[string]string{"memory": "500"},
24272428
},
24282429
Properties: map[string]string{configs.QuotaPreemptionDelay: "50ms"},
2429-
}, 0, true},
2430+
}, 0, true, true},
24302431
}
24312432

24322433
var oldMax *resources.Resource
@@ -3266,3 +3267,54 @@ func TestQueue_setPreemptionTime(t *testing.T) {
32663267
})
32673268
}
32683269
}
3270+
3271+
func TestOverrideAndResetPreemptionTime(t *testing.T) {
3272+
root, e := createRootQueue(nil)
3273+
assert.NilError(t, e, "failed to create basic root queue")
3274+
tests := []struct {
3275+
name string
3276+
oldMaxResource *resources.Resource
3277+
maxRes map[string]string
3278+
currentUsage *resources.Resource
3279+
delay time.Duration
3280+
timeChange bool
3281+
overrideTimeChange bool
3282+
}{
3283+
{"usage lesser than max res, so preemption time is not set. try to override", resources.NewResourceFromMap(map[string]resources.Quantity{"test": 100}), map[string]string{"test": "150"}, resources.NewResourceFromMap(map[string]resources.Quantity{"test": 80}), 5, false, false},
3284+
{"setting preemption time first, try to override", resources.NewResourceFromMap(map[string]resources.Quantity{"test": 100}), map[string]string{"test": "50"}, resources.NewResourceFromMap(map[string]resources.Quantity{"test": 80}), 10, true, false},
3285+
{"preemption time did not set first time, try to override", resources.NewResourceFromMap(map[string]resources.Quantity{"test": 100}), map[string]string{"test": "150"}, resources.NewResourceFromMap(map[string]resources.Quantity{"test": 180}), 10, false, true},
3286+
}
3287+
for _, tt := range tests {
3288+
t.Run(tt.name, func(t *testing.T) {
3289+
queue, err := createManagedQueue(root, "test", false, tt.maxRes)
3290+
assert.NilError(t, err, "queue creation failed unexpectedly")
3291+
queue.quotaPreemptionDelay = tt.delay
3292+
queue.allocatedResource = tt.currentUsage
3293+
before := queue.quotaPreemptionStartTime
3294+
queue.setPreemptionTime(tt.oldMaxResource, tt.delay)
3295+
after := queue.quotaPreemptionStartTime
3296+
if tt.timeChange {
3297+
assert.Assert(t, !before.Equal(after), "time change is expected")
3298+
assert.Assert(t, !queue.quotaPreemptionStartTime.IsZero(), "schedule is expected")
3299+
}
3300+
queue.IncAllocatedResource(resources.NewResourceFromMap(map[string]resources.Quantity{"test": 1}), true)
3301+
afterOverride := queue.quotaPreemptionStartTime
3302+
if tt.overrideTimeChange {
3303+
assert.Assert(t, !after.Equal(afterOverride), "time change is not expected")
3304+
assert.Assert(t, !queue.quotaPreemptionStartTime.IsZero(), "schedule is expected")
3305+
}
3306+
if tt.timeChange && !tt.overrideTimeChange {
3307+
assert.Assert(t, after.Equal(afterOverride), "time change is not expected")
3308+
assert.Assert(t, !queue.quotaPreemptionStartTime.IsZero(), "schedule is expected")
3309+
}
3310+
if tt.timeChange || tt.overrideTimeChange {
3311+
queue.ResetPreemptionTime()
3312+
afterReset := queue.quotaPreemptionStartTime
3313+
assert.Assert(t, !afterOverride.Equal(afterReset), "time change is expected")
3314+
assert.Assert(t, afterReset.IsZero(), "time change is expected")
3315+
assert.Assert(t, queue.quotaPreemptionDelay == 0, "time change is expected")
3316+
assert.Assert(t, queue.quotaPreemptionStartTime.IsZero(), "schedule is not expected")
3317+
}
3318+
})
3319+
}
3320+
}

pkg/scheduler/objects/quota_preemptor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestQuotaChangeGetPreemptableResource(t *testing.T) {
6565
t.Run(tc.name, func(t *testing.T) {
6666
tc.queue.parent.guaranteedResource = tc.parentGuaranteed
6767
tc.queue.maxResource = tc.maxResource
68-
tc.queue.IncAllocatedResource(tc.usedResource)
68+
tc.queue.IncAllocatedResource(tc.usedResource, false)
6969
preemptor := NewQuotaPreemptor(tc.queue)
7070
preemptor.setPreemptableResources()
7171
assert.Equal(t, resources.Equals(preemptor.preemptableResource, tc.preemptable), true)

0 commit comments

Comments
 (0)