Skip to content

Commit 3b47b63

Browse files
committed
[YUNIKORN-3121] Replace REST endpoint with orderLog in state dump
1 parent 07283a2 commit 3b47b63

9 files changed

Lines changed: 163 additions & 296 deletions

File tree

pkg/scheduler/objects/queue.go

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,49 +1337,6 @@ func (sq *Queue) sortQueues() []*Queue {
13371337
return sortedQueues
13381338
}
13391339

1340-
// GetSchedulingOrder returns a sorted shallow copy of the queues for this parent queue along with
1341-
// their eligible applications that would be tried in the current scheduling cycle.
1342-
// This follows the same logic as TryAllocate method.
1343-
// Only queues with a pending resource request are considered. The queues are sorted using the
1344-
// sorting type for the parent queue.
1345-
// Lock free call all locks are taken when needed in called functions
1346-
func (sq *Queue) GetSchedulingOrder() []*dao.SchedulingOrderDAO {
1347-
if sq.IsLeafQueue() {
1348-
// For leaf queues, return the queue with its eligible applications
1349-
appIDs := make([]string, 0)
1350-
1351-
// Process the apps (filters out app without pending requests) - same logic as TryAllocate
1352-
for _, app := range sq.sortApplications(false) {
1353-
runnableInQueue := sq.canRunApp(app.ApplicationID)
1354-
runnableByUserLimit := ugm.GetUserManager().CanRunApp(sq.QueuePath, app.ApplicationID, app.user)
1355-
app.updateRunnableStatus(runnableInQueue, runnableByUserLimit)
1356-
if app.IsAccepted() && (!runnableInQueue || !runnableByUserLimit) {
1357-
continue
1358-
}
1359-
appIDs = append(appIDs, app.ApplicationID)
1360-
}
1361-
1362-
// Only return this queue if it has eligible applications or pending resources
1363-
if len(appIDs) > 0 || resources.StrictlyGreaterThanZero(sq.GetPendingResource()) {
1364-
return []*dao.SchedulingOrderDAO{{
1365-
QueueName: sq.QueuePath,
1366-
ApplicationIDs: appIDs,
1367-
}}
1368-
}
1369-
return nil
1370-
} else {
1371-
// For parent queues, process child queues - same logic as TryAllocate
1372-
result := make([]*dao.SchedulingOrderDAO, 0)
1373-
1374-
// Process each sorted child queue using the original sortQueues method
1375-
for _, child := range sq.sortQueues() {
1376-
childInfo := child.GetSchedulingOrder()
1377-
result = append(result, childInfo...)
1378-
}
1379-
return result
1380-
}
1381-
}
1382-
13831340
// getHeadRoom returns the headroom for the queue. This can never be more than the headroom for the parent.
13841341
// In case there are no nodes in a newly started cluster and no queues have a limit configured this call
13851342
// will return nil.

pkg/scheduler/objects/queue_test.go

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3051,104 +3051,3 @@ func TestQueueBackoffProperties(t *testing.T) {
30513051
assert.Equal(t, uint64(0), leaf3.GetMaxAppUnschedAskBackoff())
30523052
assert.Equal(t, 30*time.Second, leaf3.GetBackoffDelay())
30533053
}
3054-
3055-
func TestGetSchedulingOrder(t *testing.T) {
3056-
// create the root
3057-
root, err := createRootQueue(nil)
3058-
root.sortType = policies.FifoSortPolicy
3059-
assert.NilError(t, err, "queue create failed")
3060-
3061-
// setup queue hierarchy
3062-
parent1, err := createManagedQueue(root, "parent1", true, nil)
3063-
parent1.sortType = policies.FifoSortPolicy
3064-
assert.NilError(t, err, "failed to create parent1 queue")
3065-
parent2, err := createManagedQueue(root, "parent2", true, nil)
3066-
assert.NilError(t, err, "failed to create parent2 queue")
3067-
3068-
leaf1, err := createManagedQueue(parent1, "leaf1", false, nil)
3069-
assert.NilError(t, err, "failed to create leaf1 queue")
3070-
leaf2, err := createManagedQueue(parent1, "leaf2", false, nil)
3071-
assert.NilError(t, err, "failed to create leaf2 queue")
3072-
leaf3, err := createManagedQueue(parent2, "leaf3", false, nil)
3073-
assert.NilError(t, err, "failed to create leaf3 queue")
3074-
3075-
// resource for asks
3076-
res, err := resources.NewResourceFromConf(map[string]string{"first": "1"})
3077-
assert.NilError(t, err, "failed to create basic resource")
3078-
3079-
// App1 in leaf1 with pending resources
3080-
app1 := newApplication("app1", "default", leaf1.QueuePath)
3081-
app1.queue = leaf1
3082-
leaf1.AddApplication(app1)
3083-
err = app1.AddAllocationAsk(newAllocationAsk("alloc-1", "app1", res))
3084-
assert.NilError(t, err, "failed to add allocation ask to app1")
3085-
leaf1.incPendingResource(res)
3086-
3087-
// App2 in leaf1 with no pending resources
3088-
app2 := newApplication("app2", "default", leaf1.QueuePath)
3089-
app2.queue = leaf1
3090-
leaf1.AddApplication(app2)
3091-
3092-
// App3 in leaf2 with pending resources
3093-
app3 := newApplication("app3", "default", leaf2.QueuePath)
3094-
app3.queue = leaf2
3095-
leaf2.AddApplication(app3)
3096-
err = app3.AddAllocationAsk(newAllocationAsk("alloc-3", "app3", res))
3097-
assert.NilError(t, err, "failed to add allocation ask to app3")
3098-
leaf2.incPendingResource(res)
3099-
3100-
// App4 in leaf3 with pending resources, but cannot run
3101-
app4 := newApplication("app4", "default", leaf3.QueuePath)
3102-
app4.queue = leaf3
3103-
leaf3.AddApplication(app4)
3104-
err = app4.AddAllocationAsk(newAllocationAsk("alloc-4", "app4", res))
3105-
assert.NilError(t, err, "failed to add allocation ask to app4")
3106-
leaf3.incPendingResource(res)
3107-
// Make app4 not runnable by setting queue to full
3108-
leaf3.maxRunningApps = 1
3109-
leaf3.runningApps = 1
3110-
3111-
// Get scheduling order from root
3112-
order := root.GetSchedulingOrder()
3113-
3114-
// Expected order based on FIFO:
3115-
// 1. parent1 -> leaf1 (app1)
3116-
// 2. parent1 -> leaf2 (app3)
3117-
// 3. parent2 -> leaf3 (no apps, because app4 is not runnable but queue has pending)
3118-
assert.Equal(t, len(order), 3, "incorrect number of scheduling order entries")
3119-
3120-
// Entry 1: leaf1
3121-
assert.Equal(t, order[0].QueueName, "root.parent1.leaf1")
3122-
assert.Equal(t, len(order[0].ApplicationIDs), 1, "leaf1 should have one app")
3123-
assert.Equal(t, order[0].ApplicationIDs[0], "app1")
3124-
3125-
// Entry 2: leaf2
3126-
assert.Equal(t, order[1].QueueName, "root.parent1.leaf2")
3127-
assert.Equal(t, len(order[1].ApplicationIDs), 1, "leaf2 should have one app")
3128-
assert.Equal(t, order[1].ApplicationIDs[0], "app3")
3129-
3130-
// Entry 3: leaf3
3131-
assert.Equal(t, order[2].QueueName, "root.parent2.leaf3")
3132-
assert.Equal(t, len(order[2].ApplicationIDs), 0, "leaf3 should have no runnable apps")
3133-
3134-
// Test a leaf queue directly
3135-
order = leaf1.GetSchedulingOrder()
3136-
assert.Equal(t, len(order), 1, "direct call on leaf1 failed")
3137-
assert.Equal(t, order[0].QueueName, "root.parent1.leaf1")
3138-
assert.Equal(t, len(order[0].ApplicationIDs), 1)
3139-
assert.Equal(t, order[0].ApplicationIDs[0], "app1")
3140-
3141-
// Test a leaf queue with no pending resources
3142-
leaf4, err := createManagedQueue(parent2, "leaf4", false, nil)
3143-
assert.NilError(t, err, "failed to create leaf4")
3144-
order = leaf4.GetSchedulingOrder()
3145-
assert.Equal(t, len(order), 0, "leaf queue with no pending resources should return nil/empty")
3146-
3147-
// Test a parent with no pending children
3148-
parent3, err := createManagedQueue(root, "parent3", true, nil)
3149-
assert.NilError(t, err, "failed to create parent3")
3150-
_, err = createManagedQueue(parent3, "leaf5", false, nil)
3151-
assert.NilError(t, err, "failed to create leaf5")
3152-
order = parent3.GetSchedulingOrder()
3153-
assert.Equal(t, len(order), 0, "parent with no pending children should return empty")
3154-
}

pkg/scheduler/partition.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,6 @@ func (pc *PartitionContext) GetPartitionQueues() dao.PartitionQueueDAOInfo {
507507
return partitionQueueDAOInfo
508508
}
509509

510-
// GetPartitionSchedulingOrder builds the sorted queue info for the whole queue structure to pass to the webservice
511-
func (pc *PartitionContext) GetPartitionSchedulingOrder() []*dao.SchedulingOrderDAO {
512-
return pc.root.GetSchedulingOrder()
513-
}
514-
515510
// GetPlacementRules returns the current active rule set as dao to expose to the webservice
516511
func (pc *PartitionContext) GetPlacementRules() []*dao.RuleDAO {
517512
return pc.getPlacementManager().GetRulesDAO()
@@ -1720,3 +1715,31 @@ func (pc *PartitionContext) getReservationCount() int {
17201715
defer pc.RUnlock()
17211716
return pc.reservations
17221717
}
1718+
1719+
// GetOrderLog returns a snapshot of applications with pending requests grouped by queue
1720+
func (pc *PartitionContext) GetOrderLog() []*dao.OrderLogEntry {
1721+
pc.RLock()
1722+
defer pc.RUnlock()
1723+
1724+
// Build a map of queue -> applications with pending requests
1725+
queueAppMap := make(map[string][]string)
1726+
1727+
for _, app := range pc.applications {
1728+
// Only include apps with pending resources
1729+
if resources.StrictlyGreaterThanZero(app.GetPendingResource()) {
1730+
queuePath := app.GetQueuePath()
1731+
queueAppMap[queuePath] = append(queueAppMap[queuePath], app.ApplicationID)
1732+
}
1733+
}
1734+
1735+
// Convert map to slice
1736+
result := make([]*dao.OrderLogEntry, 0, len(queueAppMap))
1737+
for queueName, appIDs := range queueAppMap {
1738+
result = append(result, &dao.OrderLogEntry{
1739+
QueueName: queueName,
1740+
ApplicationIDs: appIDs,
1741+
})
1742+
}
1743+
1744+
return result
1745+
}

pkg/scheduler/partition_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4998,3 +4998,116 @@ func TestGetPartitionQueueDAOInfo(t *testing.T) {
49984998
assert.Equal(t, "root.parent.leaf", leafDAO.QueueName)
49994999
assert.Equal(t, 0, len(leafDAO.Children))
50005000
}
5001+
5002+
func TestOrderLog(t *testing.T) {
5003+
// Create partition with multiple queues
5004+
conf := configs.PartitionConfig{
5005+
Name: "test",
5006+
Queues: []configs.QueueConfig{
5007+
{
5008+
Name: "root",
5009+
Parent: true,
5010+
SubmitACL: "*",
5011+
Queues: []configs.QueueConfig{
5012+
{Name: "default", Parent: false},
5013+
{Name: "production", Parent: false},
5014+
},
5015+
},
5016+
},
5017+
}
5018+
partition, err := newPartitionContext(conf, rmID, nil, false)
5019+
assert.NilError(t, err, "partition create failed")
5020+
5021+
// Empty partition should have empty order log
5022+
orderLog := partition.GetOrderLog()
5023+
assert.Equal(t, 0, len(orderLog), "initial order log should be empty")
5024+
5025+
// Add app1 to default queue with pending request
5026+
res := resources.NewResourceFromMap(map[string]resources.Quantity{"memory": 100, "vcore": 10})
5027+
app1 := newApplication(appID1, "default", "root.default")
5028+
err = partition.AddApplication(app1)
5029+
assert.NilError(t, err, "failed to add app1")
5030+
err = app1.AddAllocationAsk(newAllocationAsk("alloc-1", appID1, res))
5031+
assert.NilError(t, err, "failed to add ask to app1")
5032+
5033+
// Order log should show app1 in default queue
5034+
orderLog = partition.GetOrderLog()
5035+
assert.Equal(t, 1, len(orderLog), "should have 1 queue entry")
5036+
assert.Equal(t, "root.default", orderLog[0].QueueName)
5037+
assert.Equal(t, 1, len(orderLog[0].ApplicationIDs), "should have 1 app")
5038+
assert.Equal(t, appID1, orderLog[0].ApplicationIDs[0])
5039+
5040+
// Add app2 to same queue
5041+
app2 := newApplication(appID2, "default", "root.default")
5042+
err = partition.AddApplication(app2)
5043+
assert.NilError(t, err, "failed to add app2")
5044+
err = app2.AddAllocationAsk(newAllocationAsk("alloc-2", appID2, res))
5045+
assert.NilError(t, err, "failed to add ask to app2")
5046+
5047+
// Order log should show both apps in default queue
5048+
orderLog = partition.GetOrderLog()
5049+
assert.Equal(t, 1, len(orderLog), "should have 1 queue entry")
5050+
assert.Equal(t, 2, len(orderLog[0].ApplicationIDs), "should have 2 apps")
5051+
assert.Assert(t, contains(orderLog[0].ApplicationIDs, appID1), "should contain app1")
5052+
assert.Assert(t, contains(orderLog[0].ApplicationIDs, appID2), "should contain app2")
5053+
5054+
// Add app3 to production queue
5055+
app3 := newApplication(appID3, "production", "root.production")
5056+
err = partition.AddApplication(app3)
5057+
assert.NilError(t, err, "failed to add app3")
5058+
err = app3.AddAllocationAsk(newAllocationAsk("alloc-3", appID3, res))
5059+
assert.NilError(t, err, "failed to add ask to app3")
5060+
5061+
// Order log should show both queues
5062+
orderLog = partition.GetOrderLog()
5063+
assert.Equal(t, 2, len(orderLog), "should have 2 queue entries")
5064+
queueNames := make([]string, len(orderLog))
5065+
for i, entry := range orderLog {
5066+
queueNames[i] = entry.QueueName
5067+
}
5068+
assert.Assert(t, contains(queueNames, "root.default"), "should contain default queue")
5069+
assert.Assert(t, contains(queueNames, "root.production"), "should contain production queue")
5070+
5071+
// Remove allocation from app1 - it should disappear from order log
5072+
app1.RemoveAllocationAsk("alloc-1")
5073+
orderLog = partition.GetOrderLog()
5074+
assert.Equal(t, 2, len(orderLog), "should still have 2 queue entries")
5075+
for _, entry := range orderLog {
5076+
if entry.QueueName == defQueue {
5077+
assert.Equal(t, 1, len(entry.ApplicationIDs), "default queue should have 1 app")
5078+
assert.Equal(t, appID2, entry.ApplicationIDs[0], "should only have app2")
5079+
}
5080+
}
5081+
5082+
// Test snapshot immutability
5083+
snapshot1 := partition.GetOrderLog()
5084+
app4 := newApplication("app-4", "default", "root.default")
5085+
err = partition.AddApplication(app4)
5086+
assert.NilError(t, err, "failed to add app4")
5087+
err = app4.AddAllocationAsk(newAllocationAsk("alloc-4", "app-4", res))
5088+
assert.NilError(t, err, "failed to add ask to app4")
5089+
snapshot2 := partition.GetOrderLog()
5090+
5091+
// First snapshot should not be affected by new app
5092+
for _, entry := range snapshot1 {
5093+
if entry.QueueName == defQueue {
5094+
assert.Equal(t, 1, len(entry.ApplicationIDs), "snapshot1 should still have 1 app in default")
5095+
}
5096+
}
5097+
// Second snapshot should have the new app
5098+
for _, entry := range snapshot2 {
5099+
if entry.QueueName == defQueue {
5100+
assert.Equal(t, 2, len(entry.ApplicationIDs), "snapshot2 should have 2 apps in default")
5101+
}
5102+
}
5103+
}
5104+
5105+
// Helper function to check if a slice contains a string
5106+
func contains(slice []string, str string) bool {
5107+
for _, s := range slice {
5108+
if s == str {
5109+
return true
5110+
}
5111+
}
5112+
return false
5113+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
to you under the Apache License, Version 2.0 (the
77
"License"); you may not use this file except in compliance
88
with the License. You may obtain a copy of the License at
9+
910
http://www.apache.org/licenses/LICENSE-2.0
11+
1012
Unless required by applicable law or agreed to in writing, software
1113
distributed under the License is distributed on an "AS IS" BASIS,
1214
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,8 +18,8 @@
1618

1719
package dao
1820

19-
// SchedulingOrderDAO contains queue and application information for scheduling order
20-
type SchedulingOrderDAO struct {
21+
// OrderLogEntry represents applications with pending requests in a queue
22+
type OrderLogEntry struct {
2123
QueueName string `json:"queueName"`
22-
ApplicationIDs []string `json:"applicationIDs"` // Application IDs that would be tried in current scheduling cycle
24+
ApplicationIDs []string `json:"applicationIDs"` // Applications with pending allocation requests
2325
}

pkg/webservice/handlers.go

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -707,11 +707,11 @@ func getPartitionQueues(w http.ResponseWriter, r *http.Request) {
707707
buildJSONErrorResponse(w, MissingParamsName, http.StatusBadRequest)
708708
return
709709
}
710-
partitionName := vars.ByName("partition")
710+
partition := vars.ByName("partition")
711711
var partitionQueuesDAOInfo dao.PartitionQueueDAOInfo
712-
var partition = schedulerContext.Load().GetPartitionWithoutClusterID(partitionName)
713-
if partition != nil {
714-
partitionQueuesDAOInfo = partition.GetPartitionQueues()
712+
var partitionContext = schedulerContext.Load().GetPartitionWithoutClusterID(partition)
713+
if partitionContext != nil {
714+
partitionQueuesDAOInfo = partitionContext.GetPartitionQueues()
715715
} else {
716716
buildJSONErrorResponse(w, PartitionDoesNotExists, http.StatusNotFound)
717717
return
@@ -721,27 +721,6 @@ func getPartitionQueues(w http.ResponseWriter, r *http.Request) {
721721
}
722722
}
723723

724-
func getPartitionSchedulingOrder(w http.ResponseWriter, r *http.Request) {
725-
writeHeaders(w, r.Method)
726-
vars := httprouter.ParamsFromContext(r.Context())
727-
if vars == nil {
728-
buildJSONErrorResponse(w, MissingParamsName, http.StatusBadRequest)
729-
return
730-
}
731-
partitionName := vars.ByName("partition")
732-
var partitionSchedulingOrderDAOInfo []*dao.SchedulingOrderDAO
733-
var partition = schedulerContext.Load().GetPartitionWithoutClusterID(partitionName)
734-
if partition != nil {
735-
partitionSchedulingOrderDAOInfo = partition.GetPartitionSchedulingOrder()
736-
} else {
737-
buildJSONErrorResponse(w, PartitionDoesNotExists, http.StatusNotFound)
738-
return
739-
}
740-
if err := json.NewEncoder(w).Encode(partitionSchedulingOrderDAOInfo); err != nil {
741-
buildJSONErrorResponse(w, err.Error(), http.StatusInternalServerError)
742-
}
743-
}
744-
745724
func getPartitionQueue(w http.ResponseWriter, r *http.Request) {
746725
writeHeaders(w, r.Method)
747726
vars := httprouter.ParamsFromContext(r.Context())
@@ -1159,6 +1138,17 @@ func getPartitionQueuesDAO(lists map[string]*scheduler.PartitionContext) []dao.P
11591138
return result
11601139
}
11611140

1141+
func getOrderLogDAO(lists map[string]*scheduler.PartitionContext) map[string][]*dao.OrderLogEntry {
1142+
result := make(map[string][]*dao.OrderLogEntry)
1143+
1144+
for _, partition := range lists {
1145+
partitionName := common.GetPartitionNameWithoutClusterID(partition.Name)
1146+
result[partitionName] = partition.GetOrderLog()
1147+
}
1148+
1149+
return result
1150+
}
1151+
11621152
func getClusterDAO(lists map[string]*scheduler.PartitionContext) []*dao.ClusterDAOInfo {
11631153
result := make([]*dao.ClusterDAOInfo, 0, len(lists))
11641154

0 commit comments

Comments
 (0)