blob: c0792d50117e9e0ce2800057dc5166a7d7727444 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package objects
import (
"fmt"
"math/rand"
"reflect"
"sort"
"strconv"
"strings"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
promtu "github.com/prometheus/client_golang/prometheus/testutil"
"gotest.tools/v3/assert"
"github.com/apache/yunikorn-core/pkg/common"
"github.com/apache/yunikorn-core/pkg/common/configs"
"github.com/apache/yunikorn-core/pkg/common/resources"
"github.com/apache/yunikorn-core/pkg/events"
"github.com/apache/yunikorn-core/pkg/metrics"
"github.com/apache/yunikorn-core/pkg/scheduler/objects/template"
"github.com/apache/yunikorn-core/pkg/scheduler/policies"
siCommon "github.com/apache/yunikorn-scheduler-interface/lib/go/common"
"github.com/apache/yunikorn-scheduler-interface/lib/go/si"
)
const ZeroResource string = "{\"resources\":{\"first\":{\"value\":0}}}"
// base test for creating a managed queue
func TestQueueBasics(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// check the state of the queue
if !root.IsManaged() && !root.IsLeafQueue() && !root.IsRunning() {
t.Error("root queue status is incorrect")
}
// allocations should be nil
if !resources.IsZero(root.pending) {
t.Error("root queue must not have allocations set on create")
}
}
func TestManagedSubQueues(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// single parent under root
var parent *Queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
if parent.IsLeafQueue() || !parent.IsManaged() || !parent.IsRunning() {
t.Error("parent queue is not marked as running managed parent")
}
if len(root.children) == 0 {
t.Error("parent queue is not added to the root queue")
}
if parent.isRoot() {
t.Error("parent queue says it is the root queue which is incorrect")
}
if parent.RemoveQueue() || len(root.children) != 1 {
t.Error("parent queue should not have been removed as it is running")
}
// add a leaf under the parent
var leaf *Queue
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
if len(parent.children) == 0 {
t.Error("leaf queue is not added to the parent queue")
}
if !leaf.IsLeafQueue() || !leaf.IsManaged() {
t.Error("leaf queue is not marked as managed leaf")
}
// cannot remove child with app in it
app := newApplication(appID1, "default", "root.parent.leaf")
leaf.AddApplication(app)
// both parent and leaf are marked for removal
parent.MarkQueueForRemoval()
if !leaf.IsDraining() || !parent.IsDraining() {
t.Error("queues are not marked for removal (not in draining state)")
}
// try to remove the parent
if parent.RemoveQueue() {
t.Error("parent queue should not have been removed as it has a child")
}
// try to remove the child
if leaf.RemoveQueue() {
t.Error("leaf queue should not have been removed")
}
// remove the app (dirty way)
delete(leaf.applications, appID1)
if !leaf.RemoveQueue() && len(parent.children) != 0 {
t.Error("leaf queue should have been removed and parent updated and was not")
}
}
func TestDynamicSubQueues(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// single parent under root
var parent *Queue
parent, err = createDynamicQueue(root, "parent", true)
assert.NilError(t, err, "failed to create parent queue")
if parent.IsLeafQueue() || parent.IsManaged() {
t.Errorf("parent queue is not marked as parent")
}
if len(root.children) == 0 {
t.Errorf("parent queue is not added to the root queue")
}
// add a leaf under the parent
var leaf *Queue
leaf, err = createDynamicQueue(parent, "leaf", false)
assert.NilError(t, err, "failed to create leaf queue")
if len(parent.children) == 0 {
t.Error("leaf queue is not added to the parent queue")
}
if !leaf.IsLeafQueue() || leaf.IsManaged() {
t.Error("leaf queue is not marked as managed leaf")
}
// cannot remove child with app in it
app := newApplication(appID1, "default", "root.parent.leaf")
leaf.AddApplication(app)
// try to mark parent and leaf for removal
parent.MarkQueueForRemoval()
if leaf.IsDraining() || parent.IsDraining() {
t.Error("queues are marked for removal (draining state not for Dynamic queues)")
}
// try to remove the parent
if parent.RemoveQueue() {
t.Error("parent queue should not have been removed as it has a child")
}
// try to remove the child
if leaf.RemoveQueue() {
t.Error("leaf queue should not have been removed")
}
// remove the app (dirty way)
delete(leaf.applications, appID1)
if !leaf.RemoveQueue() && len(parent.children) != 0 {
t.Error("leaf queue should have been removed and parent updated and was not")
}
}
func TestPriorityCalcWithFencedQueue(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// single parent under root
var parent *Queue
parent, err = createManagedQueueWithProps(root, "parent", true, nil, map[string]string{
configs.PriorityOffset: "5",
})
assert.NilError(t, err, "failed to create parent queue")
// add a leaf under the parent
var leaf *Queue
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, map[string]string{
configs.PriorityOffset: "3",
configs.PriorityPolicy: policies.FencePriorityPolicy.String(),
})
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, parent.GetCurrentPriority(), configs.MinPriority, "initial parent priority wrong")
assert.Equal(t, leaf.GetCurrentPriority(), configs.MinPriority, "initial leaf priority wrong")
app := newApplication(appID1, "default", "root.parent.leaf")
app.SetQueue(leaf)
leaf.AddApplication(app)
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "1"})
assert.NilError(t, err, "failed to create basic resource")
err = app.AddAllocationAsk(newAllocationAskPriority("alloc-1", appID1, res, 0))
assert.NilError(t, err, "failed to add app")
assert.Equal(t, parent.GetCurrentPriority(), int32(8), "parent priority wrong after alloc add")
assert.Equal(t, leaf.GetCurrentPriority(), int32(3), "leaf priority wrong after alloc add")
app2 := newApplication(appID2, "default", "root.parent.leaf")
app2.SetQueue(leaf)
leaf.AddApplication(app2)
err = app2.AddAllocationAsk(newAllocationAskPriority("alloc-2", appID2, res, 5))
assert.NilError(t, err, "failed to add app")
assert.Equal(t, parent.GetCurrentPriority(), int32(8), "parent priority wrong after alloc add 2")
assert.Equal(t, leaf.GetCurrentPriority(), int32(3), "leaf priority wrong after alloc add 2")
leaf.RemoveApplication(app2)
assert.Equal(t, parent.GetCurrentPriority(), int32(8), "parent priority wrong after app 2 removed")
assert.Equal(t, leaf.GetCurrentPriority(), int32(3), "leaf priority wrong after app 2 removed")
leaf.RemoveApplication(app)
assert.Equal(t, parent.GetCurrentPriority(), configs.MinPriority, "final parent priority wrong")
assert.Equal(t, leaf.GetCurrentPriority(), configs.MinPriority, "final leaf priority wrong")
}
func TestPriorityCalc(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// single parent under root
var parent *Queue
parent, err = createManagedQueueWithProps(root, "parent", true, nil, map[string]string{
configs.PriorityOffset: "5",
})
assert.NilError(t, err, "failed to create parent queue")
// add a leaf under the parent
var leaf *Queue
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, map[string]string{
configs.PriorityOffset: "3",
})
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, parent.GetCurrentPriority(), configs.MinPriority, "initial parent priority wrong")
assert.Equal(t, leaf.GetCurrentPriority(), configs.MinPriority, "initial leaf priority wrong")
app := newApplication(appID1, "default", "root.parent.leaf")
app.SetQueue(leaf)
leaf.AddApplication(app)
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "1"})
assert.NilError(t, err, "failed to create basic resource")
err = app.AddAllocationAsk(newAllocationAskPriority("alloc-1", appID1, res, 0))
assert.NilError(t, err, "failed to add app")
assert.Equal(t, parent.GetCurrentPriority(), int32(8), "parent priority wrong after alloc add")
assert.Equal(t, leaf.GetCurrentPriority(), int32(3), "leaf priority wrong after alloc add")
app2 := newApplication(appID2, "default", "root.parent.leaf")
app2.SetQueue(leaf)
leaf.AddApplication(app2)
err = app2.AddAllocationAsk(newAllocationAskPriority("alloc-2", appID2, res, 5))
assert.NilError(t, err, "failed to add app")
assert.Equal(t, parent.GetCurrentPriority(), int32(13), "parent priority wrong after alloc add 2")
assert.Equal(t, leaf.GetCurrentPriority(), int32(8), "leaf priority wrong after alloc add 2")
leaf.RemoveApplication(app2)
assert.Equal(t, parent.GetCurrentPriority(), int32(8), "parent priority wrong after app 2 removed")
assert.Equal(t, leaf.GetCurrentPriority(), int32(3), "leaf priority wrong after app 2 removed")
leaf.RemoveApplication(app)
assert.Equal(t, parent.GetCurrentPriority(), configs.MinPriority, "final parent priority wrong")
assert.Equal(t, leaf.GetCurrentPriority(), configs.MinPriority, "final leaf priority wrong")
}
func TestPendingCalc(t *testing.T) {
// Reset existing metric storage; otherwise this unit test would get metrics populated by other UTs.
// In long run, to make the metrics code more testable, we should pass instantiable Metrics obj to Queue
// instead of using a global Metrics obj at pkg/metrics/init.go.
metrics.Reset()
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
res := map[string]string{"memory": "100", "vcores": "10"}
var allocRes *resources.Resource
allocRes, err = resources.NewResourceFromConf(res)
assert.NilError(t, err, "failed to create basic resource")
leaf.incPendingResource(allocRes)
if !resources.Equals(root.pending, allocRes) {
t.Errorf("root queue pending allocation failed to increment expected %v, got %v", allocRes, root.pending)
}
if !resources.Equals(leaf.pending, allocRes) {
t.Errorf("leaf queue pending allocation failed to increment expected %v, got %v", allocRes, leaf.pending)
}
metrics := []string{"yunikorn_root_queue_resource", "yunikorn_root_leaf_queue_resource"}
want := concatQueueResourceMetric(metrics, []string{`
yunikorn_root_queue_resource{resource="memory",state="pending"} 100
yunikorn_root_queue_resource{resource="vcores",state="pending"} 10
`, `
yunikorn_root_leaf_queue_resource{resource="memory",state="pending"} 100
yunikorn_root_leaf_queue_resource{resource="vcores",state="pending"} 10
`},
)
assert.NilError(t, promtu.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(want), metrics...), "unexpected metrics")
leaf.decPendingResource(allocRes)
if !resources.IsZero(root.pending) {
t.Errorf("root queue pending allocation failed to decrement expected 0, got %v", root.pending)
}
if !resources.IsZero(leaf.pending) {
t.Errorf("leaf queue pending allocation failed to decrement expected 0, got %v", leaf.pending)
}
want = concatQueueResourceMetric(metrics, []string{`
yunikorn_root_queue_resource{resource="memory",state="pending"} 0
yunikorn_root_queue_resource{resource="vcores",state="pending"} 0
`, `
yunikorn_root_leaf_queue_resource{resource="memory",state="pending"} 0
yunikorn_root_leaf_queue_resource{resource="vcores",state="pending"} 0
`},
)
assert.NilError(t, promtu.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(want), metrics...), "unexpected metrics")
// Not allowed to go negative: both will be zero after this
newRes := resources.Multiply(allocRes, 2)
root.pending = newRes
leaf.decPendingResource(newRes)
// using the get function to access the value
if !resources.IsZero(root.GetPendingResource()) {
t.Errorf("root queue pending allocation failed to decrement expected zero, got %v", root.pending)
}
if !resources.IsZero(leaf.GetPendingResource()) {
t.Errorf("leaf queue pending allocation should have failed to decrement expected zero, got %v", leaf.pending)
}
want = concatQueueResourceMetric(metrics, []string{`
yunikorn_root_queue_resource{resource="memory",state="pending"} 0
yunikorn_root_queue_resource{resource="vcores",state="pending"} 0
`, `
yunikorn_root_leaf_queue_resource{resource="memory",state="pending"} 0
yunikorn_root_leaf_queue_resource{resource="vcores",state="pending"} 0
`},
)
assert.NilError(t, promtu.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(want), metrics...), "unexpected metrics")
}
const (
QueueResourceMetricHelp = "# HELP %v Queue resource metrics. State of the resource includes `guaranteed`, `max`, `allocated`, `pending`, `preempting`."
QueueResourceMetricType = "# TYPE %v gauge"
)
func concatQueueResourceMetric(metricNames, metricVals []string) string {
var out string
for i, metricName := range metricNames {
out = out + fmt.Sprintf(QueueResourceMetricHelp, metricName) + "\n"
out = out + fmt.Sprintf(QueueResourceMetricType, metricName) + "\n"
out += strings.TrimLeft(metricVals[i], "\n")
}
return out
}
func TestGetChildQueueInfo(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var parent *Queue
parent, err = createManagedQueue(root, "parent-man", true, nil)
assert.NilError(t, err, "failed to create managed parent queue")
for i := 0; i < 10; i++ {
_, err = createManagedQueue(parent, "leaf-man"+strconv.Itoa(i), false, nil)
if err != nil {
t.Errorf("failed to create managed queue: %v", err)
}
}
if len(parent.children) != 10 {
t.Errorf("managed leaf queues are not added to the parent queue, expected 10 children got %d", len(parent.children))
}
parent, err = createDynamicQueue(root, "parent-un", true)
assert.NilError(t, err, "failed to create dynamic parent queue")
for i := 0; i < 10; i++ {
_, err = createDynamicQueue(parent, "leaf-un-"+strconv.Itoa(i), false)
if err != nil {
t.Errorf("failed to create dynamic queue: %v", err)
}
}
if len(parent.children) != 10 {
t.Errorf("dynamic leaf queues are not added to the parent queue, expected 10 children got %d", len(parent.children))
}
// check the root queue
if len(root.children) != 2 {
t.Errorf("parent queues are not added to the root queue, expected 2 children got %d", len(root.children))
}
}
func TestAddApplication(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf-man", false, nil)
assert.NilError(t, err, "failed to create managed leaf queue")
pending := resources.NewResourceFromMap(
map[string]resources.Quantity{
siCommon.Memory: 10,
})
app := newApplication(appID1, "default", "root.parent.leaf")
app.pending = pending
// adding the app must not update pending resources
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "Application was not added to the queue as expected")
assert.Assert(t, resources.IsZero(leaf.pending), "leaf queue pending resource not zero")
// add the same app again should not increase the number of apps
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "Application was not replaced in the queue as expected")
}
func TestRemoveApplication(t *testing.T) {
// create the root
root, err := createRootQueue(map[string]string{"first": "100"})
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf-man", false, nil)
assert.NilError(t, err, "failed to create managed leaf queue")
// try removing a non existing app
nonExist := newApplication("test", "", "")
leaf.RemoveApplication(nonExist)
assert.Equal(t, len(leaf.applications), 0, "Removal of non existing app updated unexpected")
// add an app and remove it
app := newApplication("exists", "default", "root.leaf-man")
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "Application was not added to the queue as expected")
assert.Assert(t, resources.IsZero(leaf.pending), "leaf queue pending resource not zero")
leaf.RemoveApplication(nonExist)
assert.Equal(t, len(leaf.applications), 1, "Non existing application was removed from the queue")
assert.Equal(t, len(leaf.GetCopyOfApps()), 1, "Non existing application was removed from the queue")
leaf.RemoveApplication(app)
assert.Equal(t, len(leaf.applications), 0, "Application was not removed from the queue as expected")
assert.Equal(t, len(leaf.GetCopyOfApps()), 0, "Application was not removed from the queue as expected")
// try the same again now with pending resources set
res := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10})
app.pending.AddTo(res)
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "Application was not added to the queue as expected")
assert.Assert(t, resources.IsZero(leaf.pending), "leaf queue pending resource not zero")
// update pending resources for the hierarchy
leaf.incPendingResource(res)
leaf.RemoveApplication(app)
assert.Equal(t, len(leaf.applications), 0, "Application was not removed from the queue as expected")
assert.Assert(t, resources.IsZero(leaf.pending), "leaf queue pending resource not updated correctly")
assert.Assert(t, resources.IsZero(root.pending), "root queue pending resource not updated correctly")
app.allocatedResource.AddTo(res)
app.pending = resources.NewResource()
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "Application was not added to the queue as expected")
assert.Assert(t, resources.IsZero(leaf.allocatedResource), "leaf queue allocated resource not zero")
// update allocated resources for the hierarchy
err = leaf.IncAllocatedResource(res, false)
assert.NilError(t, err, "increment of allocated resource on queue should not have failed")
leaf.RemoveApplication(app)
assert.Equal(t, len(leaf.applications), 0, "Application was not removed from the queue as expected")
assert.Assert(t, resources.IsZero(leaf.allocatedResource), "leaf queue allocated resource not updated correctly")
assert.Assert(t, resources.IsZero(root.allocatedResource), "root queue allocated resource not updated correctly")
// remove an application with allocated placeholders
app.allocatedPlaceholder.AddTo(res)
app.allocatedResource.SubFrom(res)
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "Application was not added to the queue as expected")
assert.Assert(t, resources.IsZero(leaf.allocatedResource), "leaf queue allocated resource not zero")
err = leaf.IncAllocatedResource(res, false)
assert.NilError(t, err, "increment of allocated resource on queue should not have failed")
assert.Assert(t, resources.Equals(leaf.allocatedResource, res), "leaf queue allocated resource not updated as expected")
leaf.RemoveApplication(app)
assert.Equal(t, len(leaf.applications), 0, "Application was not removed from the queue as expected")
assert.Assert(t, resources.IsZero(leaf.allocatedResource), "leaf queue allocated resource not updated correctly")
assert.Assert(t, resources.IsZero(root.allocatedResource), "root queue allocated resource not updated correctly")
}
func TestQueueStates(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// add a leaf under the root
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
err = leaf.handleQueueEvent(Stop)
if err != nil || !leaf.IsStopped() {
t.Errorf("leaf queue is not marked stopped: %v", err)
}
err = leaf.handleQueueEvent(Start)
if err != nil || !leaf.IsRunning() {
t.Errorf("leaf queue is not marked running: %v", err)
}
err = leaf.handleQueueEvent(Remove)
if err != nil || !leaf.IsDraining() {
t.Errorf("leaf queue is not marked draining: %v", err)
}
err = leaf.handleQueueEvent(Start)
if err != nil || !leaf.IsRunning() {
t.Errorf("leaf queue is not marked running: %v", err)
}
}
// This test must not test the sorter that is underlying.
// It tests the queue specific parts of the code only.
func TestSortApplications(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf, parent *Queue
// empty parent queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue: %v")
if apps := parent.sortApplications(false); apps != nil {
t.Errorf("parent queue should not return sorted apps: %v", apps)
}
// empty leaf queue
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
if len(leaf.sortApplications(false)) != 0 {
t.Errorf("empty queue should return no app from sort: %v", leaf)
}
// new app does not have pending res, does not get returned
app := newApplication(appID1, "default", leaf.QueuePath)
app.queue = leaf
leaf.AddApplication(app)
if len(leaf.sortApplications(false)) != 0 {
t.Errorf("app without ask should not be in sorted apps: %v", app)
}
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "1"})
assert.NilError(t, err, "failed to create basic resource")
// add an ask app must be returned
err = app.AddAllocationAsk(newAllocationAsk("alloc-1", appID1, res))
assert.NilError(t, err, "failed to add allocation ask")
sortedApp := leaf.sortApplications(false)
if len(sortedApp) != 1 || sortedApp[0].ApplicationID != appID1 {
t.Errorf("sorted application is missing expected app: %v", sortedApp)
}
// set allocated
_, err = app.AllocateAsk("alloc-1")
if err != nil || len(leaf.sortApplications(false)) != 0 {
t.Errorf("app with ask but no pending resources should not be in sorted apps: %v (err = %v)", app, err)
}
}
func TestSortAppsWithPlaceholderAllocations(t *testing.T) {
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
leaf, err := createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
app1 := newApplication(appID1, "default", leaf.QueuePath)
app1.queue = leaf
leaf.AddApplication(app1)
app2 := newApplication(appID2, "default", leaf.QueuePath)
app2.queue = leaf
leaf.AddApplication(app2)
res, err := resources.NewResourceFromConf(map[string]string{"first": "1"})
assert.NilError(t, err, "failed to create basic resource")
alloc := newAllocation(appID1, "node-0", res)
alloc.placeholder = true
// adding a placeholder allocation & pending request to "app1"
app1.AddAllocation(alloc)
err = app1.AddAllocationAsk(newAllocationAsk("ask-0", appID1, res))
assert.NilError(t, err, "could not add ask")
phApps := leaf.sortApplications(true)
assert.Equal(t, 1, len(phApps))
// adding a placeholder allocation & pending request to "app2"
alloc2 := newAllocation(appID2, "node-1", res)
alloc2.placeholder = true
app2.AddAllocation(alloc2)
err = app2.AddAllocationAsk(newAllocationAsk("ask-0", appID1, res))
assert.NilError(t, err, "could not add ask")
phApps = leaf.sortApplications(true)
assert.Equal(t, 2, len(phApps))
}
// This test must not test the sorter that is underlying.
// It tests the queue specific parts of the code only.
func TestSortQueue(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf, parent *Queue
// empty parent queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
if len(parent.sortQueues()) != 0 {
t.Errorf("parent queue should return sorted queues: %v", parent)
}
// leaf queue must be nil
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
if queues := leaf.sortQueues(); queues != nil {
t.Errorf("leaf queue should return sorted queues: %v", queues)
}
if queues := parent.sortQueues(); len(queues) != 0 {
t.Errorf("parent queue with leaf returned unexpectd queues: %v", queues)
}
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "1"})
assert.NilError(t, err, "failed to create basic resource")
leaf.incPendingResource(res)
if queues := parent.sortQueues(); len(queues) != 1 {
t.Errorf("parent queue did not return expected queues: %v", queues)
}
err = leaf.handleQueueEvent(Stop)
assert.NilError(t, err, "failed to stop queue")
if queues := parent.sortQueues(); len(queues) != 0 {
t.Errorf("parent queue returned stopped queue: %v", queues)
}
}
func TestHeadroom(t *testing.T) {
// create the root: nil test
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
headRoom := root.getHeadRoom()
if headRoom != nil {
t.Errorf("empty cluster with root queue should not have headroom: %v", headRoom)
}
var parent *Queue
// empty parent queue: nil test
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
headRoom = parent.getHeadRoom()
if headRoom != nil {
t.Errorf("empty cluster with parent queue should not have headroom: %v", headRoom)
}
// recreate the structure to pick up changes on max etc
// structure is:
// root max resource 20,10; alloc 10,6 head 10,4
// - parent max resource 20,8; alloc 10,6 head 10,2
// - leaf1 max resource ---; alloc 5,3 head 10,2 * parent headroom used
// - leaf2 max resource ---; alloc 5,3 head 10,2 * parent headroom used
// set the max on the root
resMap := map[string]string{"first": "20", "second": "10"}
root, err = createRootQueue(resMap)
assert.NilError(t, err, "failed to create root queue with limit")
// set the max on the parent
resMap = map[string]string{"first": "20", "second": "8"}
parent, err = createManagedQueue(root, "parent", true, resMap)
assert.NilError(t, err, "failed to create parent queue")
// leaf1 queue no limit
var leaf1, leaf2 *Queue
leaf1, err = createManagedQueue(parent, "leaf1", false, nil)
assert.NilError(t, err, "failed to create leaf1 queue")
// leaf2 queue no limit
leaf2, err = createManagedQueue(parent, "leaf2", false, nil)
assert.NilError(t, err, "failed to create leaf2 queue")
// allocating and allocated
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "3"})
assert.NilError(t, err, "failed to create resource")
err = leaf1.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf1")
err = leaf2.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf2")
// headRoom root should be this (max 20-10 - alloc 10-6)
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "4"})
assert.NilError(t, err, "failed to create resource")
headRoom = root.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "root queue head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom := root.getMaxHeadRoom()
assert.Assert(t, maxHeadRoom == nil, "root queue max headroom should be nil")
// headRoom parent should be this (max 20-10 - alloc 8-6)
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "2"})
assert.NilError(t, err, "failed to create resource")
headRoom = parent.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "parent queue head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom = parent.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, maxHeadRoom), "parent queue max head room not as expected %v, got: %v", res, maxHeadRoom)
// headRoom for any leaves will be at most the parent headroom
// since leaf1 does not have a max it will be the same
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "2"})
assert.NilError(t, err, "failed to create resource")
headRoom = leaf1.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "leaf1 queue head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom = leaf1.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, maxHeadRoom), "leaf1 queue max head room not as expected %v, got: %v", res, maxHeadRoom)
// since leaf2 does not have a max it will be the same
headRoom = leaf2.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "leaf2 queue head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom = leaf2.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, maxHeadRoom), "leaf2 queue max head room not as expected %v, got: %v", res, maxHeadRoom)
}
func TestHeadroomMerge(t *testing.T) {
// recreate the structure, set max capacity in parent and a leaf queue
// structure is:
// root (max: nil) (alloc a:5, b:5, c:10, d:5) (headroom: nil)
// - parent (max: a:20, b:10) (alloc a:5, b:5, c:10, d:5) (headroom a:15 b:5)
// - leaf1 (max: a:10, c:10) (alloc a:5, b:5, c:5) (headroom a:5 b:5 c:5)
// - leaf2 (max: d:10) (alloc c:5 d:5) (headroom a:15 b:5 d:5)
resMap := map[string]string{"first": "20", "second": "10"}
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create root queue with limit")
var parent, leaf1, leaf2 *Queue
parent, err = createManagedQueue(root, "parent", true, resMap)
assert.NilError(t, err, "failed to create parent queue")
resMap = map[string]string{"first": "10", "third": "10"}
leaf1, err = createManagedQueue(parent, "leaf1", false, resMap)
assert.NilError(t, err, "failed to create leaf1 queue")
resMap = map[string]string{"fourth": "10"}
leaf2, err = createManagedQueue(parent, "leaf2", false, resMap)
assert.NilError(t, err, "failed to create leaf2 queue")
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "5", "third": "5"})
assert.NilError(t, err, "failed to create resource")
err = leaf1.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf1")
res, err = resources.NewResourceFromConf(map[string]string{"third": "5", "fourth": "5"})
assert.NilError(t, err, "failed to create resource")
err = leaf2.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf2")
// root headroom should be nil
headRoom := root.getHeadRoom()
assert.Assert(t, headRoom == nil, "headRoom of root should be nil because no max set")
maxHeadRoom := root.getMaxHeadRoom()
assert.Assert(t, maxHeadRoom == nil, "maxHeadRoom of root should be nil because no max")
res, err = resources.NewResourceFromConf(map[string]string{"first": "15", "second": "5"})
assert.NilError(t, err, "failed to create resource")
headRoom = parent.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "parent queue max head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom = parent.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, maxHeadRoom), "parent queue max head room not as expected %v, got: %v", res, maxHeadRoom)
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "5", "third": "5"})
assert.NilError(t, err, "failed to create resource")
headRoom = leaf1.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "leaf1 queue head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom = leaf1.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, maxHeadRoom), "leaf1 queue max head room not as expected %v, got: %v", res, maxHeadRoom)
res, err = resources.NewResourceFromConf(map[string]string{"first": "15", "second": "5", "fourth": "5"})
assert.NilError(t, err, "failed to create resource")
headRoom = leaf2.getHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "leaf2 queue head room not as expected %v, got: %v", res, headRoom)
maxHeadRoom = leaf2.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, maxHeadRoom), "leaf2 queue max head room not as expected %v, got: %v", res, maxHeadRoom)
}
func TestMaxHeadroomNoMax(t *testing.T) {
// create the root: nil test
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
headRoom := root.getMaxHeadRoom()
if headRoom != nil {
t.Errorf("empty cluster with root queue should not have headroom: %v", headRoom)
}
var parent *Queue
// empty parent queue: nil test
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
headRoom = parent.getMaxHeadRoom()
if headRoom != nil {
t.Errorf("empty cluster with parent queue should not have headroom: %v", headRoom)
}
// recreate the structure, all queues have no max capacity set
// structure is:
// root (max: nil)
// - parent (max: nil)
// - leaf1 (max: nil) (alloc: 5,3)
// - leaf2 (max: nil) (alloc: 5,3)
root, err = createRootQueue(nil)
assert.NilError(t, err, "failed to create root queue with limit")
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
var leaf1, leaf2 *Queue
leaf1, err = createManagedQueue(parent, "leaf1", false, nil)
assert.NilError(t, err, "failed to create leaf1 queue")
leaf2, err = createManagedQueue(parent, "leaf2", false, nil)
assert.NilError(t, err, "failed to create leaf2 queue")
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "3"})
assert.NilError(t, err, "failed to create resource")
err = leaf1.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf1")
err = leaf2.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf2")
headRoom = root.getMaxHeadRoom()
assert.Assert(t, headRoom == nil, "headRoom of root should be nil because no max set for all queues")
headRoom = parent.getMaxHeadRoom()
assert.Assert(t, headRoom == nil, "headRoom of parent should be nil because no max set for all queues")
headRoom = leaf1.getMaxHeadRoom()
assert.Assert(t, headRoom == nil, "headRoom of leaf1 should be nil because no max set for all queues")
headRoom = leaf2.getMaxHeadRoom()
assert.Assert(t, headRoom == nil, "headRoom of leaf2 should be nil because no max set for all queues")
}
func TestMaxHeadroomMax(t *testing.T) {
// recreate the structure, set max capacity in parent and a leaf queue
// structure is:
// root (max: nil)
// - parent (max: 20,8)
// - leaf1 (max: 10, 8) (alloc: 5,3)
// - leaf2 (max: nil) (alloc: 6,4)
resMap := map[string]string{"first": "20", "second": "8"}
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create root queue with limit")
var parent, leaf1, leaf2 *Queue
parent, err = createManagedQueue(root, "parent", true, resMap)
assert.NilError(t, err, "failed to create parent queue")
resMap = map[string]string{"first": "10", "second": "8"}
leaf1, err = createManagedQueue(parent, "leaf1", false, resMap)
assert.NilError(t, err, "failed to create leaf1 queue")
leaf2, err = createManagedQueue(parent, "leaf2", false, nil)
assert.NilError(t, err, "failed to create leaf2 queue")
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "3"})
assert.NilError(t, err, "failed to create resource")
err = leaf1.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf1")
err = leaf2.IncAllocatedResource(res, true)
assert.NilError(t, err, "failed to set allocated resource on leaf2")
// root headroom should be nil
headRoom := root.getMaxHeadRoom()
assert.Assert(t, headRoom == nil, "headRoom of root should be nil because no max set for all queues")
// parent headroom = parentMax - leaf1Allocated - leaf2Allocated
// parent headroom = (20 - 5 - 5, 8 - 3 - 3) = (10, 2)
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "2"})
assert.NilError(t, err, "failed to create resource")
headRoom = parent.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "parent queue head room not as expected %v, got: %v", res, headRoom)
// leaf1 headroom = MIN(parentHeadRoom, leaf1Max - leaf1Allocated)
// leaf1 headroom = MIN((10,2), (10-5, 8-3)) = MIN((10,2), (5,5)) = MIN(5, 2)
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "2"})
assert.NilError(t, err, "failed to create resource")
headRoom = leaf1.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "leaf1 queue head room not as expected %v, got: %v", res, headRoom)
// leaf2 headroom = parentMax - leaf1Allocated - leaf2Allocated
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "2"})
assert.NilError(t, err, "failed to create resource")
headRoom = leaf2.getMaxHeadRoom()
assert.Assert(t, resources.Equals(res, headRoom), "leaf2 queue head room not as expected %v, got: %v", res, headRoom)
}
func TestGetMaxResource(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
maxUsage := root.GetMaxResource()
if maxUsage != nil {
t.Errorf("empty cluster with root queue should not have max set: %v", maxUsage)
}
var parent *Queue
// empty parent queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
maxUsage = parent.GetMaxResource()
if maxUsage != nil {
t.Errorf("empty cluster parent queue should not have max set: %v", maxUsage)
}
// set the max on the root: recreate the structure to pick up changes
var res *resources.Resource
resMap := map[string]string{"first": "10", "second": "5"}
res, err = resources.NewResourceFromConf(resMap)
assert.NilError(t, err, "failed to create resource")
root, err = createRootQueue(resMap)
assert.NilError(t, err, "failed to create root queue with limit")
maxUsage = root.GetMaxResource()
if !resources.Equals(res, maxUsage) {
t.Errorf("root queue should have max set expected %v, got: %v", res, maxUsage)
}
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
maxUsage = parent.GetMaxResource()
if !resources.Equals(res, maxUsage) {
t.Errorf("parent queue should have max from root set expected %v, got: %v", res, maxUsage)
}
// leaf queue with limit: contrary to root should get min from both merged
var leaf *Queue
resMap = map[string]string{"first": "5", "second": "10"}
leaf, err = createManagedQueue(parent, "leaf", false, resMap)
assert.NilError(t, err, "failed to create leaf queue")
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "5"})
assert.NilError(t, err, "failed to create resource")
maxUsage = leaf.GetMaxResource()
assert.Assert(t, resources.Equals(res, maxUsage), "leaf queue should have merged max set expected %v, got: %v", res, maxUsage)
// replace parent with one with limit on different resource
resMap = map[string]string{"third": "2"}
parent, err = createManagedQueue(root, "parent2", true, resMap)
assert.NilError(t, err, "failed to create parent2 queue")
res, err = resources.NewResourceFromConf(map[string]string{"first": "10", "second": "5", "third": "2"})
assert.NilError(t, err, "failed to create resource")
maxUsage = parent.GetMaxResource()
assert.Assert(t, resources.Equals(res, maxUsage), "parent2 queue should have max from root set expected %v, got: %v", res, maxUsage)
resMap = map[string]string{"first": "5", "second": "10"}
leaf, err = createManagedQueue(parent, "leaf2", false, resMap)
assert.NilError(t, err, "failed to create leaf2 queue")
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "5", "third": "2"})
assert.NilError(t, err, "failed to create resource")
maxUsage = leaf.GetMaxResource()
assert.Assert(t, resources.Equals(res, maxUsage), "leaf2 queue should have reset merged max set expected %v, got: %v", res, maxUsage)
}
func TestGetMaxQueueSet(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var nilRes *resources.Resource
assert.Equal(t, root.GetMaxQueueSet(), nilRes, "root queue should always return max set nil")
var parent *Queue
// empty parent queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
assert.Equal(t, parent.GetMaxQueueSet(), nilRes, "parent queue should not have max")
// set the max on the root: recreate the structure to pick up changes
resMap := map[string]string{"first": "10", "second": "10"}
assert.NilError(t, err, "failed to create resource")
root, err = createRootQueue(resMap)
assert.NilError(t, err, "failed to create root queue with limit set")
assert.Equal(t, root.GetMaxQueueSet(), nilRes, "root queue should always return max set nil")
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
assert.Equal(t, parent.GetMaxQueueSet(), nilRes, "parent queue should not have max even with root set")
// leaf queue with limit
// parent has no limit and root is ignored: expect the leaf limit returned
var leaf *Queue
resMap = map[string]string{"first": "5", "second": "5"}
leaf, err = createManagedQueue(parent, "leaf", false, resMap)
assert.NilError(t, err, "failed to create leaf queue")
var res *resources.Resource
res, err = resources.NewResourceFromConf(resMap)
assert.NilError(t, err, "failed to create resource")
maxSet := leaf.GetMaxQueueSet()
assert.Assert(t, resources.Equals(res, maxSet), "leaf queue should have max set expected %v, got: %v", res, maxSet)
// replace parent with one with limit on multiple resource
resMap = map[string]string{"second": "5", "third": "2"}
parent, err = createManagedQueue(root, "parent2", true, resMap)
assert.NilError(t, err, "failed to create parent2 queue")
res, err = resources.NewResourceFromConf(resMap)
assert.NilError(t, err, "failed to create resource")
maxSet = parent.GetMaxQueueSet()
assert.Assert(t, resources.Equals(res, maxSet), "parent2 queue should have max excluding root expected %v, got: %v", res, maxSet)
// a leaf with max set on different resource than the parent.
// The parent has limit and root is ignored: expect the merged parent and leaf to be returned
resMap = map[string]string{"first": "5", "second": "10"}
leaf, err = createManagedQueue(parent, "leaf2", false, resMap)
assert.NilError(t, err, "failed to create leaf2 queue")
res, err = resources.NewResourceFromConf(map[string]string{"first": "5", "second": "5", "third": "2"})
assert.NilError(t, err, "failed to create resource")
maxSet = leaf.GetMaxQueueSet()
assert.Assert(t, resources.Equals(res, maxSet), "leaf2 queue should have reset merged max set expected %v, got: %v", res, maxSet)
}
func TestReserveApp(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, len(leaf.reservedApps), 0, "new queue should not have reserved apps")
// no checks this works for everything
appName := "something"
leaf.Reserve(appName)
assert.Equal(t, len(leaf.reservedApps), 1, "app should have been reserved")
assert.Equal(t, leaf.reservedApps[appName], 1, "app should have one reservation")
leaf.Reserve(appName)
assert.Equal(t, leaf.reservedApps[appName], 2, "app should have two reservations")
leaf.UnReserve(appName, 2)
assert.Equal(t, len(leaf.reservedApps), 0, "queue should not have any reserved apps, all reservations were removed")
leaf.UnReserve("unknown", 1)
assert.Equal(t, len(leaf.reservedApps), 0, "unreserve of unknown app should not have changed count or added app")
}
func TestGetApp(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
// check for init of the map
if unknown := leaf.GetApplication("unknown"); unknown != nil {
t.Errorf("un registered app found using appID which should not happen: %v", unknown)
}
// add app and check proper returns
app := newApplication(appID1, "default", leaf.QueuePath)
leaf.AddApplication(app)
assert.Equal(t, len(leaf.applications), 1, "queue should have one app registered")
if leaf.GetApplication(appID1) == nil {
t.Errorf("registered app not found using appID")
}
if unknown := leaf.GetApplication("unknown"); unknown != nil {
t.Errorf("un registered app found using appID which should not happen: %v", unknown)
}
}
func TestIsEmpty(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
assert.Equal(t, root.IsEmpty(), true, "new root queue should have been empty")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, root.IsEmpty(), false, "root queue with child leaf should not have been empty")
assert.Equal(t, leaf.IsEmpty(), true, "new leaf should have been empty")
// add app and check proper returns
app := newApplication(appID1, "default", leaf.QueuePath)
leaf.AddApplication(app)
assert.Equal(t, leaf.IsEmpty(), false, "queue with registered app should not be empty")
}
func TestGetOutstandingRequestMax(t *testing.T) {
// queue structure:
// root (max.cpu = 5)
// - queue1 (max.cpu = 10)
// - queue2 (max.cpu = 5)
//
// submit app1 to root.queue1, app2 to root.queue2
// app1 asks for 20 1x1CPU requests, app2 asks for 20 1x1CPU requests
// verify the outstanding requests for each of the queue is up to its max capacity
// root max is irrelevant for the calculation
// root: 15, queue1: 10 and queue2: 5
// add an allocation for 5 CPU to queue1 and check the reduced numbers
// root: 10, queue1: 5 and queue2: 5
testOutstanding(t, map[string]string{"cpu": "1"}, map[string]string{"cpu": "5"})
}
func TestGetOutstandingUntracked(t *testing.T) {
// same test as TestGetOutstandingRequestMax but adding an unlimited resource to the
// allocations to make sure it does not affect the calculations
// queue structure:
// root (max.cpu = 5, pods = 10)
// - queue1 (max.cpu = 10)
// - queue2 (max.cpu = 5)
//
// submit app1 to root.queue1, app2 to root.queue2
// app1 asks for 20 1x1CPU, 1xPOD requests, app2 asks for 20 1x1CPU, 1xPOD requests
// verify the outstanding requests for each of the queue is up to its max capacity
// root max is irrelevant for the calculation
// root: 15, queue1: 10 and queue2: 5
// add an allocation for 5 CPU to queue1 and check the reduced numbers
// root: 10, queue1: 5 and queue2: 5
testOutstanding(t, map[string]string{"cpu": "1", "pods": "2"}, map[string]string{"cpu": "5", "pods": "10"})
}
func testOutstanding(t *testing.T, allocMap, usedMap map[string]string) {
root, err := createRootQueue(usedMap)
var queue1, queue2 *Queue
assert.NilError(t, err, "failed to create root queue with limit")
queue1, err = createManagedQueue(root, "queue1", false, map[string]string{"cpu": "10"})
assert.NilError(t, err, "failed to create queue1 queue")
queue2, err = createManagedQueue(root, "queue2", false, map[string]string{"cpu": "5"})
assert.NilError(t, err, "failed to create queue2 queue")
var allocRes, usedRes *resources.Resource
allocRes, err = resources.NewResourceFromConf(allocMap)
assert.NilError(t, err, "failed to create basic resource")
usedRes, err = resources.NewResourceFromConf(usedMap)
assert.NilError(t, err, "failed to create basic resource")
app1 := newApplication(appID1, "default", "root.queue1")
app1.queue = queue1
queue1.AddApplication(app1)
for i := 0; i < 20; i++ {
ask := newAllocationAsk(fmt.Sprintf("alloc-%d", i), appID1, allocRes)
ask.SetSchedulingAttempted(true)
err = app1.AddAllocationAsk(ask)
assert.NilError(t, err, "failed to add allocation ask")
}
app2 := newApplication(appID2, "default", "root.queue2")
app2.queue = queue2
queue2.AddApplication(app2)
for i := 0; i < 20; i++ {
ask := newAllocationAsk(fmt.Sprintf("alloc-%d", i), appID2, allocRes)
ask.SetSchedulingAttempted(true)
err = app2.AddAllocationAsk(ask)
assert.NilError(t, err, "failed to add allocation ask")
}
// verify get outstanding requests for root, and child queues
rootTotal := make([]*Allocation, 0)
root.GetQueueOutstandingRequests(&rootTotal)
assert.Equal(t, len(rootTotal), 15)
queue1Total := make([]*Allocation, 0)
queue1.GetQueueOutstandingRequests(&queue1Total)
assert.Equal(t, len(queue1Total), 10)
queue2Total := make([]*Allocation, 0)
queue2.GetQueueOutstandingRequests(&queue2Total)
assert.Equal(t, len(queue2Total), 5)
// simulate queue1 has some allocated resources
err = queue1.IncAllocatedResource(usedRes, false)
assert.NilError(t, err, "failed to increment allocated resources")
queue1Total = make([]*Allocation, 0)
queue1.GetQueueOutstandingRequests(&queue1Total)
assert.Equal(t, len(queue1Total), 5)
queue2Total = make([]*Allocation, 0)
queue2.GetQueueOutstandingRequests(&queue2Total)
assert.Equal(t, len(queue2Total), 5)
rootTotal = make([]*Allocation, 0)
root.GetQueueOutstandingRequests(&rootTotal)
assert.Equal(t, len(rootTotal), 10)
// remove app2 from queue2
queue2.RemoveApplication(app2)
queue2Total = make([]*Allocation, 0)
queue2.GetQueueOutstandingRequests(&queue2Total)
assert.Equal(t, len(queue2Total), 0)
rootTotal = make([]*Allocation, 0)
root.GetQueueOutstandingRequests(&rootTotal)
assert.Equal(t, len(rootTotal), 5)
}
func TestGetOutstandingOnlyUntracked(t *testing.T) {
// all outstanding pods use only an unlimited resource type
// max is set for a different resource type and fully allocated
// queue structure:
// root (max.cpu = 10, pods: 10)
// - queue1 (max.cpu = 10)
//
// submit app1 to root.queue1, app1 asks for 20 1xPOD requests
// verify the outstanding requests of the queue is all outstanding requests
// add an allocation that uses all limited resources
// verify the outstanding requests of the queue is still all outstanding requests
alloc, err := resources.NewResourceFromConf(map[string]string{"pods": "1"})
assert.NilError(t, err, "failed to create basic resource")
var used *resources.Resource
usedMap := map[string]string{"cpu": "10", "pods": "10"}
used, err = resources.NewResourceFromConf(usedMap)
assert.NilError(t, err, "failed to create basic resource")
var root, queue1 *Queue
root, err = createRootQueue(usedMap)
assert.NilError(t, err, "failed to create root queue with limit")
queue1, err = createManagedQueue(root, "queue1", false, map[string]string{"cpu": "10"})
assert.NilError(t, err, "failed to create queue1 queue")
app1 := newApplication(appID1, "default", "root.queue1")
app1.queue = queue1
queue1.AddApplication(app1)
for i := 0; i < 20; i++ {
ask := newAllocationAsk(fmt.Sprintf("alloc-%d", i), appID1, alloc)
ask.SetSchedulingAttempted(true)
err = app1.AddAllocationAsk(ask)
assert.NilError(t, err, "failed to add allocation ask")
}
// verify get outstanding requests for root, and child queues
rootTotal := make([]*Allocation, 0)
root.GetQueueOutstandingRequests(&rootTotal)
assert.Equal(t, len(rootTotal), 20)
queue1Total := make([]*Allocation, 0)
queue1.GetQueueOutstandingRequests(&queue1Total)
assert.Equal(t, len(queue1Total), 20)
// simulate queue1 has some allocated resources
// after allocation, the max available becomes to be 5
err = queue1.IncAllocatedResource(used, false)
assert.NilError(t, err, "failed to increment allocated resources")
queue1Total = make([]*Allocation, 0)
queue1.GetQueueOutstandingRequests(&queue1Total)
assert.Equal(t, len(queue1Total), 20)
headRoom := queue1.getHeadRoom()
assert.Assert(t, resources.IsZero(headRoom), "headroom should have been zero")
rootTotal = make([]*Allocation, 0)
root.GetQueueOutstandingRequests(&rootTotal)
assert.Equal(t, len(rootTotal), 20)
headRoom = root.getHeadRoom()
assert.Assert(t, resources.IsZero(headRoom), "headroom should have been zero")
}
func TestGetOutstandingRequestNoMax(t *testing.T) {
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create root queue with limit")
var queue1, queue2 *Queue
queue1, err = createManagedQueue(root, "queue1", false, nil)
assert.NilError(t, err, "failed to create queue1 queue")
queue2, err = createManagedQueue(root, "queue2", false, nil)
assert.NilError(t, err, "failed to create queue2 queue")
app1 := newApplication(appID1, "default", "root.queue1")
app1.queue = queue1
queue1.AddApplication(app1)
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"cpu": "1"})
assert.NilError(t, err, "failed to create basic resource")
for i := 0; i < 10; i++ {
ask := newAllocationAsk(fmt.Sprintf("alloc-%d", i), appID1, res)
ask.SetSchedulingAttempted(true)
err = app1.AddAllocationAsk(ask)
assert.NilError(t, err, "failed to add allocation ask")
}
app2 := newApplication(appID2, "default", "root.queue2")
app2.queue = queue2
queue2.AddApplication(app2)
for i := 0; i < 20; i++ {
ask := newAllocationAsk(fmt.Sprintf("alloc-%d", i), appID2, res)
ask.SetSchedulingAttempted(true)
err = app2.AddAllocationAsk(ask)
assert.NilError(t, err, "failed to add allocation ask")
}
rootTotal := make([]*Allocation, 0)
root.GetQueueOutstandingRequests(&rootTotal)
assert.Equal(t, len(rootTotal), 30)
queue1Total := make([]*Allocation, 0)
queue1.GetQueueOutstandingRequests(&queue1Total)
assert.Equal(t, len(queue1Total), 10)
queue2Total := make([]*Allocation, 0)
queue2.GetQueueOutstandingRequests(&queue2Total)
assert.Equal(t, len(queue2Total), 20)
}
func TestAllocationCalcRoot(t *testing.T) {
resMap := map[string]string{"memory": "100", "vcores": "10"}
// create the root: must set a max on the queue
root, err := createRootQueue(resMap)
assert.NilError(t, err, "failed to create basic root queue")
var res *resources.Resource
res, err = resources.NewResourceFromConf(resMap)
assert.NilError(t, err, "failed to create basic resource")
err = root.IncAllocatedResource(res, false)
assert.NilError(t, err, "root queue allocation failed on increment")
// increment again should fail
err = root.IncAllocatedResource(res, false)
if err == nil {
t.Error("root queue allocation should have failed to increment (max hit)")
}
err = root.DecAllocatedResource(res)
assert.NilError(t, err, "root queue allocation failed on decrement")
if !resources.IsZero(root.allocatedResource) {
t.Errorf("root queue allocations are not zero: %v", root.allocatedResource)
}
err = root.DecAllocatedResource(res)
if err == nil {
t.Error("root queue allocation should have failed to decrement")
}
}
func TestAllocationCalcSub(t *testing.T) {
resMap := map[string]string{"memory": "100", "vcores": "10"}
// create the root
root, err := createRootQueue(resMap)
assert.NilError(t, err, "failed to create basic root queue")
var parent *Queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
var res *resources.Resource
res, err = resources.NewResourceFromConf(resMap)
assert.NilError(t, err, "failed to create basic resource")
err = parent.IncAllocatedResource(res, false)
assert.NilError(t, err, "parent queue allocation failed on increment")
// increment again should fail
err = parent.IncAllocatedResource(res, false)
if err == nil {
t.Error("parent queue allocation should have failed to increment (root max hit)")
}
err = parent.DecAllocatedResource(res)
assert.NilError(t, err, "parent queue allocation failed on decrement")
if !resources.IsZero(root.allocatedResource) {
t.Errorf("root queue allocations are not zero: %v", root.allocatedResource)
}
err = root.DecAllocatedResource(res)
if err == nil {
t.Error("root queue allocation should have failed to decrement")
}
// add to the parent, remove from root and then try to remove from parent: root should complain
err = parent.IncAllocatedResource(res, false)
assert.NilError(t, err, "parent queue allocation failed on increment")
err = root.DecAllocatedResource(res)
assert.NilError(t, err, "root queue allocation failed on decrement")
err = parent.DecAllocatedResource(res)
if err == nil {
t.Errorf("parent queue allocation should have failed on decrement %v, %v", root.allocatedResource, parent.allocatedResource)
}
}
func TestQueueProps(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create basic root queue")
var parent *Queue
props := map[string]string{"first": "value", "second": "other value"}
parent, err = createManagedQueueWithProps(root, "parent", true, nil, props)
assert.NilError(t, err, "failed to create parent queue")
assert.Assert(t, !parent.isLeaf, "parent queue is not marked as a parent")
assert.Equal(t, len(root.children), 1, "parent queue is not added to the root queue")
assert.Equal(t, len(parent.properties), 2, "parent queue properties expected 2, got %v", parent.properties)
var leaf *Queue
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, len(parent.children), 1, "leaf queue is not added to the parent queue")
assert.Assert(t, leaf.isLeaf && leaf.isManaged, "leaf queue is not marked as managed leaf")
assert.Equal(t, len(leaf.properties), 2, "leaf queue properties size incorrect")
props = map[string]string{}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.priorityPolicy, policies.DefaultPriorityPolicy)
assert.Equal(t, leaf.priorityOffset, int32(0))
assert.Equal(t, leaf.prioritySortEnabled, true)
props = map[string]string{"priority.policy": "default", "priority.offset": "3", "application.sort.priority": "enabled"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.priorityPolicy, policies.DefaultPriorityPolicy)
assert.Equal(t, leaf.priorityOffset, int32(3))
assert.Equal(t, leaf.prioritySortEnabled, true)
props = map[string]string{"priority.policy": "fence", "priority.offset": "-3", "application.sort.priority": "disabled"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.priorityPolicy, policies.FencePriorityPolicy)
assert.Equal(t, leaf.priorityOffset, int32(-3))
assert.Equal(t, leaf.prioritySortEnabled, false)
props = map[string]string{"priority.policy": "invalid", "priority.offset": "invalid", "application.sort.priority": "invalid"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.priorityPolicy, policies.DefaultPriorityPolicy)
assert.Equal(t, leaf.priorityOffset, int32(0))
assert.Equal(t, leaf.prioritySortEnabled, true)
props = map[string]string{"preemption.policy": "default", "preemption.delay": "10s"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.preemptionPolicy, policies.DefaultPreemptionPolicy)
assert.Equal(t, leaf.preemptionDelay, time.Second*10)
props = map[string]string{"preemption.policy": "disabled", "preemption.delay": "-1s"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.preemptionPolicy, policies.DisabledPreemptionPolicy)
assert.Equal(t, leaf.preemptionDelay, time.Second*30)
props = map[string]string{"preemption.policy": "fence", "preemption.delay": "xxxx"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.preemptionPolicy, policies.FencePreemptionPolicy)
assert.Equal(t, leaf.preemptionDelay, time.Second*30)
props = map[string]string{"preemption.policy": "invalid"}
leaf, err = createManagedQueueWithProps(parent, "leaf", false, nil, props)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.preemptionPolicy, policies.DefaultPreemptionPolicy)
}
func TestInheritedQueueProps(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create basic root queue")
var parent *Queue
props := map[string]string{
"key": "value",
"priority.policy": "fence",
"priority.offset": "100",
"preemption.policy": "fence",
}
parent, err = createManagedQueueWithProps(root, "parent", true, nil, props)
assert.NilError(t, err, "failed to create parent queue")
assert.Equal(t, parent.properties["key"], "value")
assert.Equal(t, parent.properties["priority.policy"], "fence")
assert.Equal(t, parent.properties["priority.offset"], "100")
assert.Equal(t, parent.properties["preemption.policy"], "fence")
assert.Equal(t, parent.priorityPolicy, policies.FencePriorityPolicy)
assert.Equal(t, parent.priorityOffset, int32(100))
assert.Equal(t, parent.preemptionPolicy, policies.FencePreemptionPolicy)
var leaf *Queue
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.properties["key"], "value")
assert.Equal(t, leaf.properties["priority.policy"], "default")
assert.Equal(t, leaf.properties["priority.offset"], "0")
assert.Equal(t, leaf.priorityPolicy, policies.DefaultPriorityPolicy)
assert.Equal(t, leaf.priorityOffset, int32(0))
assert.Equal(t, leaf.preemptionPolicy, policies.DefaultPreemptionPolicy)
props = map[string]string{
"preemption.policy": "disabled",
}
parent, err = createManagedQueueWithProps(root, "parent", true, nil, props)
assert.NilError(t, err, "failed to create parent queue")
assert.Equal(t, parent.preemptionPolicy, policies.DisabledPreemptionPolicy)
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
assert.Equal(t, leaf.preemptionPolicy, policies.DisabledPreemptionPolicy)
}
func TestMaxResource(t *testing.T) {
resMap := map[string]string{"first": "10"}
res, err := resources.NewResourceFromConf(resMap)
assert.NilError(t, err, "failed to create basic resource")
// create the root
var root, parent *Queue
root, err = createRootQueue(nil)
assert.NilError(t, err, "failed to create basic root queue")
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create basic managed parent queue")
// Nothing set max should be nil
if root.GetMaxResource() != nil || parent.GetMaxResource() != nil {
t.Errorf("empty cluster should not have max set on root queue")
}
// try setting on the parent (nothing should change)
parent.SetMaxResource(res)
if parent.GetMaxResource() != nil {
t.Errorf("parent queue change should have been rejected parent: %v", parent.GetMaxResource())
}
// Set on the root should change
root.SetMaxResource(res)
if !resources.Equals(res, root.GetMaxResource()) {
t.Errorf("root max setting not picked up by parent queue expected %v, got %v", res, parent.GetMaxResource())
}
}
func TestSupportTaskGroup(t *testing.T) {
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create basic root queue: %v", err)
assert.Assert(t, !root.SupportTaskGroup(), "root queue should not support task group (parent)")
// parent with sort policy set
properties := map[string]string{configs.ApplicationSortPolicy: "fifo"}
var parent *Queue
parent, err = createManagedQueueWithProps(root, "parent", true, nil, properties)
assert.NilError(t, err, "failed to create queue: %v", err)
assert.Assert(t, !parent.SupportTaskGroup(), "parent queue (FIFO policy) should not support task group")
var leaf *Queue
leaf, err = createManagedQueueWithProps(parent, "leaf1", false, nil, properties)
assert.NilError(t, err, "failed to create queue: %v", err)
assert.Assert(t, leaf.SupportTaskGroup(), "leaf queue (FIFO policy) should support task group")
properties = map[string]string{configs.ApplicationSortPolicy: "fair"}
leaf, err = createManagedQueueWithProps(parent, "leaf3", false, nil, properties)
assert.NilError(t, err, "failed to create queue: %v", err)
assert.Assert(t, !leaf.SupportTaskGroup(), "leaf queue (FAIR policy) should not support task group")
}
func TestGetPartitionQueueDAOInfo(t *testing.T) {
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create basic root queue: %v", err)
// test properties
root.properties = getProperties()
assert.DeepEqual(t, root.properties, root.GetPartitionQueueDAOInfo(true).Properties)
// test template
root.template, err = template.FromConf(&configs.ChildTemplate{
MaxApplications: uint64(1),
Properties: getProperties(),
Resources: configs.Resources{
Max: getResourceConf(),
Guaranteed: getResourceConf(),
},
})
assert.NilError(t, err)
assert.Equal(t, root.template.GetMaxApplications(), root.GetPartitionQueueDAOInfo(true).TemplateInfo.MaxApplications)
assert.DeepEqual(t, root.template.GetProperties(), root.GetPartitionQueueDAOInfo(true).TemplateInfo.Properties)
assert.DeepEqual(t, root.template.GetMaxResource().DAOMap(), root.GetPartitionQueueDAOInfo(true).TemplateInfo.MaxResource)
assert.DeepEqual(t, root.template.GetGuaranteedResource().DAOMap(), root.GetPartitionQueueDAOInfo(true).TemplateInfo.GuaranteedResource)
// test resources
root.maxResource = getResource(t)
root.guaranteedResource = getResource(t)
assert.DeepEqual(t, root.GetMaxResource().DAOMap(), root.GetPartitionQueueDAOInfo(true).MaxResource)
assert.DeepEqual(t, root.GetGuaranteedResource().DAOMap(), root.GetPartitionQueueDAOInfo(true).GuaranteedResource)
assert.DeepEqual(t, root.getHeadRoom().DAOMap(), root.GetPartitionQueueDAOInfo(true).HeadRoom)
// test allocatingAcceptedApps
root.allocatingAcceptedApps = getAllocatingAcceptedApps()
assert.Equal(t, len(root.allocatingAcceptedApps), 2, "allocatingAcceptedApps size")
assert.Equal(t, len(root.GetPartitionQueueDAOInfo(true).AllocatingAcceptedApps), 1, "AllocatingAcceptedApps size")
assert.Equal(t, root.GetPartitionQueueDAOInfo(true).AllocatingAcceptedApps[0], appID1)
// Test specific queue
_, err = createManagedQueue(root, "leaf-queue", false, nil)
assert.NilError(t, err, "failed to create managed queue")
assert.Equal(t, root.GetPartitionQueueDAOInfo(false).QueueName, "root")
assert.Equal(t, len(root.GetPartitionQueueDAOInfo(false).Children), 0)
assert.Equal(t, len(root.GetPartitionQueueDAOInfo(false).ChildNames), 1)
assert.Equal(t, root.GetPartitionQueueDAOInfo(false).ChildNames[0], "root.leaf-queue")
// Test hierarchy queue
assert.Equal(t, root.GetPartitionQueueDAOInfo(true).QueueName, "root")
assert.Equal(t, len(root.GetPartitionQueueDAOInfo(true).Children), 1)
assert.Equal(t, len(root.GetPartitionQueueDAOInfo(true).ChildNames), 1)
assert.Equal(t, root.GetPartitionQueueDAOInfo(true).Children[0].QueueName, "root.leaf-queue")
assert.Equal(t, root.GetPartitionQueueDAOInfo(true).ChildNames[0], "root.leaf-queue")
}
func getAllocatingAcceptedApps() map[string]bool {
allocatingAcceptedApps := make(map[string]bool)
allocatingAcceptedApps[appID1] = true
allocatingAcceptedApps[appID2] = false
return allocatingAcceptedApps
}
func getResourceConf() map[string]string {
resource := make(map[string]string)
resource["memory"] = strconv.Itoa(rand.Intn(10000) + 100) //nolint:gosec
return resource
}
func getResource(t *testing.T) *resources.Resource {
r, err := resources.NewResourceFromConf(getResourceConf())
assert.NilError(t, err, "failed to parse resource: %v", err)
return r
}
func getZeroResourceConf() map[string]string {
resource := make(map[string]string)
resource["memory"] = "0"
resource["vcore"] = "0"
return resource
}
func getProperties() map[string]string {
properties := make(map[string]string)
properties[strconv.Itoa(rand.Intn(10000))] = strconv.Itoa(rand.Intn(10000)) //nolint:gosec
return properties
}
func TestSetResources(t *testing.T) {
queue, err := createManagedQueueWithProps(nil, "tmp", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue queue: %v", err)
guaranteedResource := getResourceConf()
maxResource := getResourceConf()
// case 0: normal case
err = queue.setResourcesFromConf(configs.Resources{
Guaranteed: guaranteedResource,
Max: maxResource,
})
assert.NilError(t, err, "failed to set resources: %v", err)
expectedGuaranteedResource, err := resources.NewResourceFromConf(guaranteedResource)
assert.NilError(t, err, "failed to parse resource: %v", err)
assert.DeepEqual(t, queue.guaranteedResource, expectedGuaranteedResource)
expectedMaxResource, err := resources.NewResourceFromConf(maxResource)
assert.NilError(t, err, "failed to parse resource: %v", err)
assert.DeepEqual(t, queue.maxResource, expectedMaxResource)
// case 1: empty resource would set the queue resources to 'nil' if it has been set already
var nilResource *resources.Resource = nil
err = queue.setResourcesFromConf(configs.Resources{
Guaranteed: make(map[string]string),
Max: make(map[string]string),
})
assert.NilError(t, err, "failed to set resources: %v", err)
assert.DeepEqual(t, queue.guaranteedResource, nilResource)
assert.DeepEqual(t, queue.maxResource, nilResource)
// case 2: zero resource won't change the queue resources as it is 'nil' already
err = queue.setResourcesFromConf(configs.Resources{
Guaranteed: getZeroResourceConf(),
Max: getZeroResourceConf(),
})
assert.NilError(t, err, "failed to set resources: %v", err)
assert.DeepEqual(t, queue.guaranteedResource, nilResource)
assert.DeepEqual(t, queue.maxResource, nilResource)
}
func TestPreemptingResource(t *testing.T) {
one, err := resources.NewResourceFromConf(map[string]string{"memory": "10000000", "vcores": "1"})
assert.NilError(t, err, "failed to create resource")
two, err := resources.NewResourceFromConf(map[string]string{"memory": "20000000", "vcores": "2"})
assert.NilError(t, err, "failed to create resource")
three, err := resources.NewResourceFromConf(map[string]string{"memory": "30000000", "vcores": "3"})
assert.NilError(t, err, "failed to create resource")
queue, err := createManagedQueueWithProps(nil, "tmp", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue queue: %v", err)
assert.Check(t, resources.IsZero(queue.GetPreemptingResource()), "initial value should be zero")
queue.IncPreemptingResource(one)
assert.Check(t, resources.Equals(one, queue.GetPreemptingResource()), "wrong value after increment")
queue.IncPreemptingResource(two)
assert.Check(t, resources.Equals(three, queue.GetPreemptingResource()), "wrong value after increment")
queue.DecPreemptingResource(one)
assert.Check(t, resources.Equals(two, queue.GetPreemptingResource()), "wrong value after decrement")
queue.DecPreemptingResource(two)
assert.Check(t, resources.IsZero(queue.GetPreemptingResource()), "final value should be zero")
}
func TestPreemptionDelay(t *testing.T) {
queue, err := createManagedQueueWithProps(nil, "tmp", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue queue: %v", err)
assert.Equal(t, configs.DefaultPreemptionDelay, queue.GetPreemptionDelay(), "initial preemption delay incorrect")
twice := 2 * configs.DefaultPreemptionDelay
queue.preemptionDelay = twice
assert.Equal(t, twice, queue.GetPreemptionDelay(), "preemption delay not updated correctly")
}
func TestFindQueueByAppID(t *testing.T) {
root, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create queue")
parent1, err := createManagedQueue(root, "parent1", true, nil)
assert.NilError(t, err, "failed to create queue")
parent2, err := createManagedQueue(root, "parent2", true, nil)
assert.NilError(t, err, "failed to create queue")
leaf1, err := createManagedQueue(parent1, "leaf1", false, nil)
assert.NilError(t, err, "failed to create queue")
leaf2, err := createManagedQueue(parent2, "leaf2", false, nil)
assert.NilError(t, err, "failed to create queue")
app := newApplication(appID1, "default", "root.parent.leaf")
app.pending = resources.NewResourceFromMap(map[string]resources.Quantity{siCommon.Memory: 10})
leaf1.AddApplication(app)
// we should be able to find the queue from any other given the appID
assert.Equal(t, leaf1, root.FindQueueByAppID(appID1), "failed to find queue from root")
assert.Equal(t, leaf1, parent1.FindQueueByAppID(appID1), "failed to find queue from parent1")
assert.Equal(t, leaf1, parent2.FindQueueByAppID(appID1), "failed to find queue from parent2")
assert.Equal(t, leaf1, leaf1.FindQueueByAppID(appID1), "failed to find queue from leaf1")
assert.Equal(t, leaf1, leaf2.FindQueueByAppID(appID1), "failed to find queue from leaf2")
// non-existent queue should be nil
var none *Queue = nil
assert.Equal(t, none, root.FindQueueByAppID("missing"), "found queue reference in root")
assert.Equal(t, none, parent1.FindQueueByAppID("missing"), "found queue reference in parent1")
assert.Equal(t, none, parent2.FindQueueByAppID("missing"), "found queue reference in parent2")
assert.Equal(t, none, leaf1.FindQueueByAppID("missing"), "found queue reference in leaf1")
assert.Equal(t, none, leaf2.FindQueueByAppID("missing"), "found queue reference in leaf2")
}
// nolint: funlen
func TestFindEligiblePreemptionVictims(t *testing.T) {
res := resources.NewResourceFromMap(map[string]resources.Quantity{siCommon.Memory: 100})
parentMax := map[string]string{siCommon.Memory: "200"}
parentGuar := map[string]string{siCommon.Memory: "100"}
ask := createAllocationAsk("ask1", appID1, true, true, 0, res)
ask2 := createAllocationAsk("ask2", appID2, true, true, -1000, res)
alloc2 := markAllocated(nodeID1, ask2)
ask3 := createAllocationAsk("ask3", appID2, true, true, -1000, res)
alloc3 := markAllocated(nodeID1, ask3)
root, err := createRootQueue(map[string]string{siCommon.Memory: "1000"})
assert.NilError(t, err, "failed to create queue")
parent1, err := createManagedQueueGuaranteed(root, "parent1", true, parentMax, parentGuar)
assert.NilError(t, err, "failed to create queue")
parent2, err := createManagedQueueGuaranteed(root, "parent2", true, parentMax, parentGuar)
assert.NilError(t, err, "failed to create queue")
leaf1, err := createManagedQueueGuaranteed(parent1, "leaf1", false, nil, nil)
assert.NilError(t, err, "failed to create queue")
leaf2, err := createManagedQueueGuaranteed(parent2, "leaf2", false, nil, nil)
assert.NilError(t, err, "failed to create queue")
// verify no victims when no allocations exist
snapshot := leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 5, len(snapshot), "wrong snapshot count") // leaf1, parent1, root
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
// add two lower-priority allocs in leaf2
app2 := newApplication(appID2, "default", "root.parent.leaf")
app2.pending = res
leaf2.AddApplication(app2)
app2.SetQueue(leaf2)
err = app2.AddAllocationAsk(ask2)
assert.NilError(t, err, "failed to add ask")
err = app2.AddAllocationAsk(ask3)
assert.NilError(t, err, "failed to add ask")
app2.AddAllocation(alloc2)
app2.AddAllocation(alloc3)
err = leaf2.IncAllocatedResource(alloc2.allocatedResource, false)
assert.NilError(t, err, "failed to inc allocated resources")
err = leaf2.IncAllocatedResource(alloc3.allocatedResource, false)
assert.NilError(t, err, "failed to inc allocated resources")
// verify victims
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 5, len(snapshot), "wrong snapshot count") // leaf1, parent1, root
assert.Equal(t, 2, len(victims(snapshot)), "wrong victim count")
assert.Equal(t, alloc2.allocationKey, victims(snapshot)[0].allocationKey, "wrong alloc")
assert.Equal(t, alloc3.allocationKey, victims(snapshot)[1].allocationKey, "wrong alloc")
// disabling preemption on victim queue should remove victims from consideration
leaf2.preemptionPolicy = policies.DisabledPreemptionPolicy
assert.Equal(t, leaf1.findPreemptionFenceRoot(make(map[string]int64), int64(ask.priority)).QueuePath, "root")
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
leaf2.preemptionPolicy = policies.DefaultPreemptionPolicy
// fencing parent1 queue should limit scope
parent1.preemptionPolicy = policies.FencePreemptionPolicy
assert.Equal(t, leaf1.findPreemptionFenceRoot(make(map[string]int64), int64(ask.priority)).QueuePath, "root.parent1")
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
parent1.preemptionPolicy = policies.DefaultPreemptionPolicy
// fencing leaf1 queue should limit scope
leaf1.preemptionPolicy = policies.FencePreemptionPolicy
assert.Equal(t, leaf1.findPreemptionFenceRoot(make(map[string]int64), int64(ask.priority)).QueuePath, "root.parent1.leaf1")
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
leaf1.preemptionPolicy = policies.DefaultPreemptionPolicy
// fencing parent2 queue should not limit scope
parent2.preemptionPolicy = policies.FencePreemptionPolicy
assert.Equal(t, leaf1.findPreemptionFenceRoot(make(map[string]int64), int64(ask.priority)).QueuePath, "root")
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 2, len(victims(snapshot)), "wrong victim count")
parent2.preemptionPolicy = policies.DefaultPreemptionPolicy
// fencing leaf2 queue should not limit scope
leaf2.preemptionPolicy = policies.FencePreemptionPolicy
assert.Equal(t, leaf1.findPreemptionFenceRoot(make(map[string]int64), int64(ask.priority)).QueuePath, "root")
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 5, len(snapshot), "wrong victim count")
assert.Equal(t, 2, len(victims(snapshot)), "wrong victim count")
leaf2.preemptionPolicy = policies.DefaultPreemptionPolicy
// fencing using max resources and usage comparison check
// parent1 queue is full. usage has reached max resources.
// even though root.parent1 and root.parent1.leaf1 policy is DefaultPreemptionPolicy, still root.parent1 would be fenced
used := parent1.allocatedResource
parent1.allocatedResource = parent1.maxResource
assert.Equal(t, parent1.preemptionPolicy, policies.DefaultPreemptionPolicy)
assert.Equal(t, leaf1.preemptionPolicy, policies.DefaultPreemptionPolicy)
assert.Equal(t, leaf1.findPreemptionFenceRoot(make(map[string]int64), int64(ask.priority)).QueuePath, parent1.QueuePath)
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "wrong victim count")
parent1.allocatedResource = used
// requiring a specific node take alloc out of consideration
alloc2.SetRequiredNode(nodeID1)
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 1, len(victims(snapshot)), "wrong victim count")
assert.Equal(t, alloc3.allocationKey, victims(snapshot)[0].allocationKey, "wrong alloc")
alloc2.SetRequiredNode("")
// placeholder which has been marked released should not be considered
alloc2.released = true
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 1, len(victims(snapshot)), "wrong victim count")
assert.Equal(t, alloc3.allocationKey, victims(snapshot)[0].allocationKey, "wrong alloc")
alloc2.released = false
// setting priority offset on parent2 queue should remove leaf2 victims
parent2.priorityOffset = 1001
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
parent2.priorityOffset = 0
// priority-fencing parent2 with positive offset should remove leaf2 victims
parent2.priorityOffset = 1
parent2.priorityPolicy = policies.FencePriorityPolicy
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
parent2.priorityOffset = 0
parent2.priorityPolicy = policies.DefaultPriorityPolicy
// priority-fencing parent2 with negative offset should not affect leaf2 victims
parent2.priorityOffset = -1
parent2.priorityPolicy = policies.FencePriorityPolicy
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 2, len(victims(snapshot)), "wrong victim count")
parent2.priorityOffset = 0
parent2.priorityPolicy = policies.DefaultPriorityPolicy
// priority-fencing parent1 with small negative offset should not affect leaf2 victims
parent1.priorityOffset = -1000
parent1.priorityPolicy = policies.FencePriorityPolicy
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 2, len(victims(snapshot)), "wrong victim count")
parent1.priorityOffset = 0
parent1.priorityPolicy = policies.DefaultPriorityPolicy
// priority-fencing parent1 with larger negative offset should remove leaf2 victims
parent1.priorityOffset = -1001
parent1.priorityPolicy = policies.FencePriorityPolicy
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
parent1.priorityOffset = 0
parent1.priorityPolicy = policies.DefaultPriorityPolicy
// increasing parent2 guaranteed resources should remove leaf2 victims
parent2.guaranteedResource = resources.NewResourceFromMap(map[string]resources.Quantity{siCommon.Memory: 200})
snapshot = leaf1.FindEligiblePreemptionVictims(leaf1.QueuePath, ask)
assert.Equal(t, 0, len(victims(snapshot)), "found victims")
parent2.guaranteedResource = resources.NewResourceFromMap(map[string]resources.Quantity{siCommon.Memory: 100})
}
func victims(snapshot map[string]*QueuePreemptionSnapshot) []*Allocation {
results := make([]*Allocation, 0)
for _, entry := range snapshot {
results = append(results, entry.PotentialVictims...)
}
sort.SliceStable(results, func(i, j int) bool {
return results[i].allocationKey < results[j].allocationKey
})
return results
}
func TestSetTemplate(t *testing.T) {
queue, err := createManagedQueueWithProps(nil, "tmp", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue queue: %v", err)
maxApplications := uint64(1)
properties := getProperties()
guaranteedResource := getResourceConf()
expectedGuaranteedResource, err := resources.NewResourceFromConf(guaranteedResource)
assert.NilError(t, err, "failed to parse resource: %v", err)
maxResource := getResourceConf()
expectedMaxResource, err := resources.NewResourceFromConf(maxResource)
assert.NilError(t, err, "failed to parse resource: %v", err)
checkTemplate := func(queue *Queue) {
assert.Equal(t, queue.template.GetMaxApplications(), maxApplications)
assert.DeepEqual(t, queue.template.GetProperties(), properties)
assert.DeepEqual(t, queue.template.GetGuaranteedResource(), expectedGuaranteedResource)
assert.DeepEqual(t, queue.template.GetMaxResource(), expectedMaxResource)
}
// case 0: normal case
err = queue.setTemplate(configs.ChildTemplate{
MaxApplications: maxApplications,
Properties: properties,
Resources: configs.Resources{
Guaranteed: guaranteedResource,
Max: maxResource,
},
})
assert.NilError(t, err, "failed to set resources: %v", err)
checkTemplate(queue)
// case 1: empty config does nothing
err = queue.setTemplate(configs.ChildTemplate{
Properties: make(map[string]string),
Resources: configs.Resources{
Guaranteed: make(map[string]string),
Max: make(map[string]string),
},
})
assert.NilError(t, err)
assert.Assert(t, queue.template == nil)
}
func TestApplyTemplate(t *testing.T) {
childTemplate, err := template.FromConf(&configs.ChildTemplate{
MaxApplications: uint64(1),
Properties: getProperties(),
Resources: configs.Resources{
Max: getResourceConf(),
Guaranteed: getResourceConf(),
},
})
assert.NilError(t, err)
// case 0: leaf queue can apply template
leaf, err := createManagedQueueWithProps(nil, "tmp", false, nil, nil)
assert.NilError(t, err, "failed to create basic queue queue: %v", err)
leaf.applyTemplate(childTemplate)
assert.Assert(t, leaf.template == nil)
assert.Equal(t, leaf.maxRunningApps, childTemplate.GetMaxApplications())
assert.DeepEqual(t, leaf.properties, childTemplate.GetProperties())
assert.DeepEqual(t, leaf.guaranteedResource, childTemplate.GetGuaranteedResource())
assert.DeepEqual(t, leaf.maxResource, childTemplate.GetMaxResource())
// case 1: zero resource template generates nil resource
leaf2, err := createManagedQueueWithProps(nil, "tmp", false, nil, nil)
assert.NilError(t, err, "failed to create basic queue queue: %v", err)
zeroTemplate, err := template.FromConf(&configs.ChildTemplate{
Properties: getProperties(),
Resources: configs.Resources{
Max: getZeroResourceConf(),
Guaranteed: getZeroResourceConf(),
},
})
assert.NilError(t, err)
leaf2.applyTemplate(zeroTemplate)
assert.Assert(t, leaf2.maxRunningApps == 0)
assert.Assert(t, leaf2.template == nil)
assert.Assert(t, leaf2.maxResource == nil)
assert.Assert(t, leaf2.guaranteedResource == nil)
}
func TestApplyConf(t *testing.T) {
// cover error cases
errQueue, err := createManagedQueueWithProps(nil, "errConf", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue: %v", err)
// wrong submitACL
errConf1 := configs.QueueConfig{
SubmitACL: "error Submit ACL",
}
err = errQueue.ApplyConf(errConf1)
assert.ErrorContains(t, err, "multiple spaces found in ACL")
// wrong AdminACL
errConf2 := configs.QueueConfig{
AdminACL: "error Admin ACL",
}
err = errQueue.ApplyConf(errConf2)
assert.ErrorContains(t, err, "multiple spaces found in ACL")
// wrong ChildTemplate
errConf3 := configs.QueueConfig{
Parent: true,
ChildTemplate: configs.ChildTemplate{
Resources: configs.Resources{
Guaranteed: map[string]string{"wrong template": "-100"},
},
},
}
err = errQueue.ApplyConf(errConf3)
assert.ErrorContains(t, err, "invalid quantity")
// isManaged is changed from unmanaged to managed
childConf := configs.QueueConfig{}
parent, err := createManagedQueueWithProps(nil, "parent", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue: %v", err)
child, err := NewDynamicQueue("child", true, parent)
assert.NilError(t, err, "failed to create basic queue: %v", err)
err = child.ApplyConf(childConf)
assert.NilError(t, err, "failed to parse conf: %v", err)
assert.Equal(t, child.IsManaged(), true)
// isLeaf is set to false while Queues length > 0
parentConf := configs.QueueConfig{
Queues: []configs.QueueConfig{
{
Name: "child",
Parent: true,
Queues: nil,
},
},
}
parent, err = createManagedQueueWithProps(nil, "parent", false, nil, nil)
assert.NilError(t, err, "failed to create basic queue: %v", err)
err = parent.ApplyConf(parentConf)
assert.NilError(t, err, "failed to parse conf: %v", err)
assert.Equal(t, parent.IsLeafQueue(), false)
conf := configs.QueueConfig{
SubmitACL: "",
AdminACL: "",
Resources: configs.Resources{
Max: getResourceConf(),
Guaranteed: getResourceConf(),
},
ChildTemplate: configs.ChildTemplate{
Properties: make(map[string]string),
Resources: configs.Resources{
Max: getResourceConf(),
Guaranteed: getResourceConf(),
},
},
MaxApplications: 100,
}
// case 0: leaf can't set template
leaf, err := createManagedQueueWithProps(nil, "tmp", false, nil, nil)
assert.NilError(t, err, "failed to create basic queue: %v", err)
validTemplate, err := template.FromConf(&conf.ChildTemplate)
assert.NilError(t, err, "failed to parse conf: %v", err)
assert.Assert(t, validTemplate != nil)
conf.Parent = false
err = leaf.ApplyConf(conf)
assert.NilError(t, err, "failed to apply conf: %v", err)
assert.Assert(t, leaf.template == nil)
assert.Assert(t, leaf.maxResource != nil)
assert.Assert(t, leaf.guaranteedResource != nil)
assert.Equal(t, leaf.maxRunningApps, uint64(100))
// case 1-1: non-leaf queue can have template
queue, err := createManagedQueueWithProps(nil, "tmp", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue: %v", err)
conf.Parent = true
err = queue.ApplyConf(conf)
assert.NilError(t, err, "failed to apply conf: %v", err)
assert.Assert(t, queue.template != nil)
assert.Assert(t, queue.maxResource != nil)
assert.Assert(t, queue.guaranteedResource != nil)
assert.Equal(t, queue.maxRunningApps, uint64(100))
// root can't set resources
root, err := createManagedQueueWithProps(nil, "root", true, nil, nil)
assert.NilError(t, err, "failed to create basic queue: %v", err)
err = queue.ApplyConf(conf)
assert.NilError(t, err, "failed to apply conf: %v", err)
assert.Assert(t, root.maxResource == nil)
assert.Assert(t, root.guaranteedResource == nil)
assert.Equal(t, root.maxRunningApps, uint64(0))
}
func TestNewConfiguredQueue(t *testing.T) {
// check variable assignment
properties := getProperties()
resourceConf := getResourceConf()
// turn resouce config into resource struct
resourceStruct, err := resources.NewResourceFromConf(resourceConf)
assert.NilError(t, err, "failed to create new resource from config: %v", err)
parentConfig := configs.QueueConfig{
Name: "PARENT_QUEUE",
Parent: true,
MaxApplications: uint64(32),
ChildTemplate: configs.ChildTemplate{
Properties: properties,
Resources: configs.Resources{
Max: resourceConf,
Guaranteed: resourceConf,
},
},
}
parent, err := NewConfiguredQueue(parentConfig, nil)
assert.NilError(t, err, "failed to create queue: %v", err)
assert.Equal(t, parent.Name, "parent_queue")
assert.Equal(t, parent.QueuePath, "parent_queue")
assert.Equal(t, parent.isManaged, true)
assert.Equal(t, parent.maxRunningApps, uint64(32))
assert.DeepEqual(t, properties, parent.template.GetProperties())
assert.Assert(t, resources.Equals(resourceStruct, parent.template.GetMaxResource()))
assert.Assert(t, resources.Equals(resourceStruct, parent.template.GetGuaranteedResource()))
// case 0: managed leaf queue can't use template
leafConfig := configs.QueueConfig{
Name: "leaf_queue",
Parent: false,
Properties: getProperties(),
Resources: configs.Resources{
Max: getResourceConf(),
Guaranteed: getResourceConf(),
},
}
childLeaf, err := NewConfiguredQueue(leafConfig, parent)
assert.NilError(t, err, "failed to create queue: %v", err)
assert.Equal(t, childLeaf.QueuePath, "parent_queue.leaf_queue")
assert.Assert(t, childLeaf.template == nil)
assert.Assert(t, reflect.DeepEqual(childLeaf.properties, leafConfig.Properties))
childLeafMax, err := resources.NewResourceFromConf(leafConfig.Resources.Max)
assert.NilError(t, err, "Resource creation failed")
assert.Assert(t, resources.Equals(childLeaf.maxResource, childLeafMax))
childLeafGuaranteed, err := resources.NewResourceFromConf(leafConfig.Resources.Guaranteed)
assert.NilError(t, err, "Resource creation failed")
assert.Assert(t, resources.Equals(childLeaf.guaranteedResource, childLeafGuaranteed))
// case 1: non-leaf can't use template but it can inherit template from parent
NonLeafConfig := configs.QueueConfig{
Name: "nonleaf_queue",
Parent: true,
}
childNonLeaf, err := NewConfiguredQueue(NonLeafConfig, parent)
assert.NilError(t, err, "failed to create queue: %v", err)
assert.Equal(t, childNonLeaf.QueuePath, "parent_queue.nonleaf_queue")
assert.Assert(t, reflect.DeepEqual(childNonLeaf.template, parent.template))
assert.Equal(t, len(childNonLeaf.properties), 0)
assert.Assert(t, childNonLeaf.guaranteedResource == nil)
assert.Assert(t, childNonLeaf.maxResource == nil)
}
func TestResetRunningState(t *testing.T) {
emptyConf := configs.QueueConfig{
Name: "not_used",
}
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// single parent under root
var parent *Queue
parent, err = createManagedQueue(root, "parent", true, nil)
assert.NilError(t, err, "failed to create parent queue")
var leaf *Queue
leaf, err = createManagedQueue(parent, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
if len(parent.children) == 0 {
t.Error("leaf queue is not added to the parent queue")
}
parent.MarkQueueForRemoval()
assert.Assert(t, parent.IsDraining(), "parent should be marked as draining")
assert.Assert(t, leaf.IsDraining(), "leaf should be marked as draining")
err = parent.applyConf(emptyConf)
assert.NilError(t, err, "failed to update parent")
assert.Assert(t, parent.IsRunning(), "parent should be running again")
assert.Assert(t, leaf.IsDraining(), "leaf should still be marked as draining")
err = leaf.applyConf(emptyConf)
assert.NilError(t, err, "failed to update leaf")
assert.Assert(t, leaf.IsRunning(), "leaf should be running again")
}
func TestNewRecoveryQueue(t *testing.T) {
var err error
if _, err = NewRecoveryQueue(nil); err == nil {
t.Fatalf("recovery queue creation should fail with nil parent")
}
parent, err := createManagedQueueWithProps(nil, "parent", true, nil, nil)
assert.NilError(t, err, "failed to create queue: %v", err)
if _, err = NewRecoveryQueue(parent); err == nil {
t.Fatalf("recovery queue creation should fail with non-root parent")
}
parentConfig := configs.QueueConfig{
Name: "root",
Parent: true,
Properties: map[string]string{configs.ApplicationSortPolicy: "fair"},
ChildTemplate: configs.ChildTemplate{Properties: map[string]string{configs.ApplicationSortPolicy: "fair"}},
}
parent, err = NewConfiguredQueue(parentConfig, nil)
assert.NilError(t, err, "failed to create queue: %v", err)
recoveryQueue, err := NewRecoveryQueue(parent)
assert.NilError(t, err, "failed to create recovery queue: %v", err)
assert.Equal(t, common.RecoveryQueueFull, recoveryQueue.GetQueuePath(), "wrong queue name")
assert.Equal(t, policies.FifoSortPolicy, recoveryQueue.getSortType(), "wrong sort type")
}
func TestNewDynamicQueueDoesNotCreateRecovery(t *testing.T) {
parent, err := createRootQueue(nil)
assert.NilError(t, err, "failed to create queue: %v", err)
if _, err := NewDynamicQueue(common.RecoveryQueue, true, parent); err == nil {
t.Fatalf("invalid recovery queue %s was created", common.RecoveryQueueFull)
}
}
func TestNewDynamicQueue(t *testing.T) {
parent, err := createManagedQueueWithProps(nil, "parent", true, nil, nil)
assert.NilError(t, err, "failed to create queue: %v", err)
parent.template, err = template.FromConf(&configs.ChildTemplate{
MaxApplications: uint64(1),
Properties: getProperties(),
Resources: configs.Resources{
Max: getResourceConf(),
Guaranteed: getResourceConf(),
},
})
assert.NilError(t, err)
// case 0: leaf can use template
childLeaf, err := NewDynamicQueue("leaf", true, parent)
assert.NilError(t, err, "failed to create dynamic queue: %v", err)
assert.Assert(t, childLeaf.template == nil)
assert.Equal(t, childLeaf.maxRunningApps, parent.template.GetMaxApplications())
assert.DeepEqual(t, childLeaf.properties, parent.template.GetProperties())
assert.DeepEqual(t, childLeaf.maxResource, parent.template.GetMaxResource())
assert.DeepEqual(t, childLeaf.guaranteedResource, parent.template.GetGuaranteedResource())
assert.Assert(t, childLeaf.prioritySortEnabled)
assert.Equal(t, childLeaf.priorityPolicy, policies.DefaultPriorityPolicy)
assert.Equal(t, childLeaf.preemptionPolicy, policies.DefaultPreemptionPolicy)
// case 1: non-leaf can't use template but it can inherit template from parent
childNonLeaf, err := NewDynamicQueue("nonleaf_Test-a_b_#_c_#_d_/_e@dom:ain", false, parent)
assert.NilError(t, err, "failed to create dynamic queue: %v", err)
assert.Assert(t, reflect.DeepEqual(childNonLeaf.template, parent.template))
assert.Equal(t, len(childNonLeaf.properties), 0)
assert.Assert(t, childNonLeaf.guaranteedResource == nil)
assert.Assert(t, childNonLeaf.maxResource == nil)
assert.Assert(t, childNonLeaf.prioritySortEnabled)
assert.Equal(t, childNonLeaf.priorityPolicy, policies.DefaultPriorityPolicy)
assert.Equal(t, childNonLeaf.preemptionPolicy, policies.DefaultPreemptionPolicy)
// case 2: invalid queue name
_, err = NewDynamicQueue("invalid!queue", false, parent)
if err == nil {
t.Errorf("new dynamic queue should have failed to create, err is %v", err)
}
}
func TestTemplateIsNotOverrideByParent(t *testing.T) {
parent, err := createManagedQueueWithProps(nil, "parent", true, nil, nil)
assert.NilError(t, err)
parent.template, err = template.FromConf(&configs.ChildTemplate{
Properties: map[string]string{
"k": "v",
},
})
assert.NilError(t, err)
leaf, err := createManagedQueueWithProps(nil, "leaf", false, nil, nil)
assert.NilError(t, err)
leaf.template, err = template.FromConf(&configs.ChildTemplate{
Properties: map[string]string{
"k0": "v0",
},
})
assert.NilError(t, err)
err = parent.addChildQueue(leaf)
assert.NilError(t, err)
assert.Assert(t, !reflect.DeepEqual(leaf.template, parent.template))
}
func TestGetHeadRoomFromTwoQueues(t *testing.T) {
allocatedResource := &resources.Resource{
Resources: map[string]resources.Quantity{
"vcore": 2000,
"memory": 2000,
},
}
parent, err := createManagedQueueWithProps(nil, "parent", true, nil, nil)
assert.NilError(t, err)
parent.maxResource = &resources.Resource{
Resources: map[string]resources.Quantity{
"vcore": 3000,
"memory": 3000,
},
}
// make sure parent queue see all allocated resources
parent.allocatedResource = allocatedResource
// this child is not set with max memory, so it should follow parent max memory
child, err := createManagedQueueWithProps(parent, "child", true, nil, nil)
assert.NilError(t, err)
child.maxResource = &resources.Resource{
Resources: map[string]resources.Quantity{
"vcore": 3000,
},
}
child.allocatedResource = allocatedResource
result := child.getHeadRoom()
assert.Equal(t, resources.Quantity(1000), result.Resources["vcore"])
assert.Equal(t, resources.Quantity(1000), result.Resources["memory"])
}
func TestGetHeadRoomFromThreeQueues(t *testing.T) {
allocatedResource := &resources.Resource{
Resources: map[string]resources.Quantity{
"vcore": 2000,
"memory": 2000,
"pods": 1,
"other": 1,
},
}
rootQueue, err := createManagedQueueWithProps(nil, "rootQueue", true, nil, nil)
assert.NilError(t, err)
rootQueue.maxResource = &resources.Resource{
Resources: map[string]resources.Quantity{
"vcore": 3000,
"memory": 3000,
"other": 100,
},
}
// make sure rootQueue queue see all allocated resources
rootQueue.allocatedResource = allocatedResource
parent, err := createManagedQueueWithProps(rootQueue, "parent", true, nil, nil)
assert.NilError(t, err)
// this parent has a limit for one specific resource
parent.maxResource = &resources.Resource{
Resources: make(map[string]resources.Quantity),
}
parent.maxResource = &resources.Resource{
Resources: map[string]resources.Quantity{
"pods": 100,
},
}
// make sure parent queue see all allocated resources
parent.allocatedResource = allocatedResource
// this child is not set with max memory, so it should follow rootQueue max memory
child, err := createManagedQueueWithProps(parent, "child", true, nil, nil)
assert.NilError(t, err)
child.maxResource = &resources.Resource{
Resources: map[string]resources.Quantity{
"vcore": 3000,
},
}
child.allocatedResource = allocatedResource
result := child.getHeadRoom()
assert.Equal(t, resources.Quantity(1000), result.Resources["vcore"])
assert.Equal(t, resources.Quantity(1000), result.Resources["memory"])
assert.Equal(t, resources.Quantity(99), result.Resources["pods"])
assert.Equal(t, resources.Quantity(99), result.Resources["other"])
}
func TestQueue_canRunApp(t *testing.T) {
// create the root
root, err := createManagedQueueMaxApps(nil, "root", true, nil, 1)
assert.NilError(t, err, "queue create failed")
var leaf, leaf2 *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
leaf2, err = createManagedQueue(root, "leaf2", false, nil)
assert.NilError(t, err, "failed to create leaf2 queue")
// ignore allocatingAcceptedApps
assert.Assert(t, leaf.canRunApp(""), "unlimited queue should be able to run app")
assert.Assert(t, root.canRunApp(""), "queue should be able to run app (root max is 1)")
root.incRunningApps("")
assert.Assert(t, !leaf.canRunApp(""), "running apps max reached on root, should be denied")
root.maxRunningApps = 2
assert.Assert(t, leaf.canRunApp(""), "root and leave allowed")
leaf.maxRunningApps = 1
leaf.incRunningApps("")
assert.Assert(t, !leaf.canRunApp(""), "leaf should not be able to run an application")
leaf2.incRunningApps("")
assert.Assert(t, !leaf2.canRunApp(""), "leaf2 should not be able to run an application (root max reached)")
defer func() {
if r := recover(); r != nil {
t.Fatal("panic on nil queue canRunApp")
}
}()
var q *Queue
q.canRunApp("")
}
func TestQueue_incRunningApps(t *testing.T) {
// create the root
root, err := createManagedQueueMaxApps(nil, "root", true, nil, 2)
assert.NilError(t, err, "queue create failed")
var leaf, leaf2 *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
leaf2, err = createManagedQueue(root, "leaf2", false, nil)
assert.NilError(t, err, "failed to create leaf2 queue")
assert.Equal(t, leaf.runningApps, uint64(0), "default max running apps is 0")
root.incRunningApps("")
assert.Equal(t, root.runningApps, uint64(1), "root should have 1 app running")
leaf.incRunningApps("")
assert.Equal(t, leaf.runningApps, uint64(1), "leaf should have 1 app running")
assert.Equal(t, root.runningApps, uint64(2), "root should have 2 apps running")
leaf.incRunningApps("")
assert.Equal(t, leaf.runningApps, uint64(2), "leaf should have 2 app running")
assert.Equal(t, root.runningApps, uint64(2), "root should not have changed")
leaf2.incRunningApps("")
assert.Equal(t, leaf2.runningApps, uint64(1), "leaf2 should have 1 app running")
assert.Equal(t, root.runningApps, uint64(2), "root should have 1 app running")
defer func() {
if r := recover(); r != nil {
t.Fatal("panic on nil queue incRunningApps")
}
}()
var q *Queue
q.incRunningApps("")
}
func TestQueue_decRunningApps(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
leaf.decRunningApps()
assert.Equal(t, leaf.runningApps, uint64(0), "default running apps is 0, should not wrap")
assert.Equal(t, root.runningApps, uint64(0), "default running apps is 0, should not wrap")
root.runningApps = 2
leaf.runningApps = 2
leaf.decRunningApps()
assert.Equal(t, leaf.runningApps, uint64(1), "leaf should have 1 app running")
assert.Equal(t, root.runningApps, uint64(1), "root should have 1 apps running")
root.decRunningApps()
assert.Equal(t, leaf.runningApps, uint64(1), "leaf should have 1 app running")
assert.Equal(t, root.runningApps, uint64(0), "root should have no apps")
leaf.decRunningApps()
assert.Equal(t, leaf.runningApps, uint64(0), "leaf should have 0 app running")
assert.Equal(t, root.runningApps, uint64(0), "root running apps is 0, should not wrap")
defer func() {
if r := recover(); r != nil {
t.Fatal("panic on nil queue decRunningApps")
}
}()
var q *Queue
q.decRunningApps()
}
func TestQueue_setAllocatingAccepted(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf, leaf2 *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
leaf2, err = createManagedQueue(root, "leaf2", false, nil)
assert.NilError(t, err, "failed to create leaf2 queue")
leaf.setAllocatingAccepted("test-1")
assert.Equal(t, len(leaf.allocatingAcceptedApps), 1, "expected 1 app in leaf list")
assert.Equal(t, len(root.allocatingAcceptedApps), 1, "expected 1 app in root list")
leaf.setAllocatingAccepted("test-1")
assert.Equal(t, len(leaf.allocatingAcceptedApps), 1, "expected no change after adding same app again")
leaf.setAllocatingAccepted("test-2")
assert.Equal(t, len(leaf.allocatingAcceptedApps), 2, "expected 2 apps in leaf list")
assert.Equal(t, len(root.allocatingAcceptedApps), 2, "expected 2 apps in root list")
leaf2.setAllocatingAccepted("test-3")
assert.Equal(t, len(leaf2.allocatingAcceptedApps), 1, "expected 1 app in leaf2 list")
assert.Equal(t, len(root.allocatingAcceptedApps), 3, "expected 3 apps in root list")
defer func() {
if r := recover(); r != nil {
t.Fatal("panic on nil queue setAllocatingAccepted")
}
}()
var q *Queue
q.setAllocatingAccepted("")
}
func TestQueueEvents(t *testing.T) {
events.Init()
eventSystem := events.GetEventSystem().(*events.EventSystemImpl) //nolint:errcheck
eventSystem.StartServiceWithPublisher(false)
queue, err := createRootQueue(nil)
queue.Name = "testQueue"
assert.NilError(t, err)
app := newApplication(appID0, "default", "root")
queue.AddApplication(app)
queue.RemoveApplication(app)
noEvents := uint64(0)
err = common.WaitForCondition(10*time.Millisecond, time.Second, func() bool {
noEvents = eventSystem.Store.CountStoredEvents()
return noEvents == 5
})
assert.NilError(t, err, "expected 5 events, got %d", noEvents)
records := eventSystem.Store.CollectEvents()
assert.Equal(t, 5, len(records), "number of events")
assert.Equal(t, si.EventRecord_QUEUE, records[2].Type)
assert.Equal(t, si.EventRecord_ADD, records[2].EventChangeType)
assert.Equal(t, si.EventRecord_QUEUE_APP, records[2].EventChangeDetail)
assert.Equal(t, si.EventRecord_QUEUE, records[3].Type)
assert.Equal(t, si.EventRecord_REMOVE, records[3].EventChangeType)
assert.Equal(t, si.EventRecord_QUEUE_APP, records[3].EventChangeDetail)
assert.Equal(t, si.EventRecord_APP, records[4].Type, "incorrect event type, expect app")
assert.Equal(t, app.ApplicationID, records[4].ObjectID, "incorrect object ID, expected application ID")
assert.Equal(t, si.EventRecord_REMOVE, records[4].EventChangeType, "incorrect change type, expected remove")
assert.Equal(t, si.EventRecord_DETAILS_NONE, records[4].EventChangeDetail, "incorrect change detail, expected none")
newConf := configs.QueueConfig{
Parent: false,
Name: "testQueue",
Resources: configs.Resources{
Guaranteed: map[string]string{
"memory": "1",
},
Max: map[string]string{
"memory": "5",
},
},
}
err = queue.ApplyConf(newConf)
assert.NilError(t, err)
err = common.WaitForCondition(10*time.Millisecond, time.Second, func() bool {
noEvents = eventSystem.Store.CountStoredEvents()
return noEvents == 3
})
assert.NilError(t, err, "expected 3 events, got %d", noEvents)
records = eventSystem.Store.CollectEvents()
assert.Equal(t, 3, len(records), "number of events")
assert.Equal(t, si.EventRecord_QUEUE, records[0].Type)
assert.Equal(t, si.EventRecord_SET, records[0].EventChangeType)
assert.Equal(t, si.EventRecord_QUEUE_TYPE, records[0].EventChangeDetail)
assert.Equal(t, si.EventRecord_QUEUE, records[1].Type)
assert.Equal(t, si.EventRecord_SET, records[1].EventChangeType)
assert.Equal(t, si.EventRecord_QUEUE_MAX, records[1].EventChangeDetail)
assert.Equal(t, si.EventRecord_QUEUE, records[2].Type)
assert.Equal(t, si.EventRecord_SET, records[2].EventChangeType)
assert.Equal(t, si.EventRecord_QUEUE_GUARANTEED, records[2].EventChangeDetail)
}
func TestQueueRunningAppsForSingleAllocationApp(t *testing.T) {
// create the root
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
// single leaf under root
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
assert.NilError(t, err, "failed to create leaf queue")
app := newApplication(appID1, "default", "root.leaf")
app.SetQueue(leaf)
leaf.AddApplication(app)
var res *resources.Resource
res, err = resources.NewResourceFromConf(map[string]string{"first": "1"})
assert.NilError(t, err, "failed to create basic resource")
ask := newAllocationAsk("ask-1", appID1, res)
err = app.AddAllocationAsk(ask)
assert.NilError(t, err, "failed to add ask")
alloc := markAllocated(nodeID1, ask)
app.AddAllocation(alloc)
assert.Equal(t, app.CurrentState(), Running.String(), "app state should be running")
assert.Equal(t, leaf.runningApps, uint64(1), "leaf should have 1 app running")
_, err = app.allocateAsk(ask)
assert.NilError(t, err, "failed to decrease pending resources")
app.RemoveAllocation(alloc.GetAllocationKey(), si.TerminationType_STOPPED_BY_RM)
assert.Equal(t, app.CurrentState(), Completing.String(), "app state should be completing")
assert.Equal(t, leaf.runningApps, uint64(0), "leaf should have 0 app running")
}
func isNewApplicationEvent(t *testing.T, app *Application, record *si.EventRecord) {
assert.Equal(t, si.EventRecord_APP, record.Type, "incorrect event type, expect app")
assert.Equal(t, app.ApplicationID, record.ObjectID, "incorrect object ID, expected application ID")
assert.Equal(t, si.EventRecord_ADD, record.EventChangeType, "incorrect change type, expected add")
assert.Equal(t, si.EventRecord_APP_NEW, record.EventChangeDetail, "incorrect change detail, expected none")
}
func TestQueue_allocatedResFits_Root(t *testing.T) {
const first = "first"
const second = "second"
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
tests := []struct {
name string
quota map[string]string
used map[string]string
change map[string]string
want bool
}{
{"all nil", nil, nil, nil, true},
{"nil max no usage", nil, nil, map[string]string{first: "1"}, false},
{"nil max set usage", nil, map[string]string{first: "1"}, map[string]string{second: "1"}, false},
{"max = usage other in alloc", map[string]string{first: "1"}, map[string]string{first: "1"}, map[string]string{second: "1"}, false},
{"max = usage same in alloc", map[string]string{first: "1"}, map[string]string{first: "1"}, map[string]string{first: "1"}, false},
{"usage over undefined max other in alloc", map[string]string{first: "1"}, map[string]string{second: "1"}, map[string]string{first: "1"}, true},
{"usage over undefined max same in alloc", map[string]string{first: "1"}, map[string]string{second: "1"}, map[string]string{second: "1"}, false},
{"partial fit", map[string]string{first: "2"}, map[string]string{first: "1", second: "1"}, map[string]string{first: "1", second: "1"}, false},
{"all fit no usage", map[string]string{first: "2", second: "2"}, nil, map[string]string{first: "1", second: "1"}, true},
{"all fit with usage", map[string]string{first: "2", second: "2"}, map[string]string{first: "1", second: "1"}, map[string]string{first: "1", second: "1"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var quota, used, change *resources.Resource
quota, err = resources.NewResourceFromConf(tt.quota)
assert.NilError(t, err, "failed to create basic resource: quota")
root.SetMaxResource(quota)
used, err = resources.NewResourceFromConf(tt.used)
assert.NilError(t, err, "failed to create basic resource: used")
root.allocatedResource = used
change, err = resources.NewResourceFromConf(tt.change)
assert.NilError(t, err, "failed to create basic resource: diff")
assert.Equal(t, root.allocatedResFits(change), tt.want, "allocatedResFits incorrect state returned")
})
}
}
func TestQueueSetMaxRunningApps(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatal("panic on nil queue setMaxRunningApps")
}
}()
queue := &Queue{}
maxApps := uint64(10)
queue.SetMaxRunningApps(maxApps)
assert.Equal(t, maxApps, queue.maxRunningApps)
queue = nil
queue.SetMaxRunningApps(maxApps)
}
func TestQueue_allocatedResFits_Other(t *testing.T) {
const first = "first"
const second = "second"
root, err := createRootQueue(nil)
assert.NilError(t, err, "queue create failed")
var leaf *Queue
leaf, err = createManagedQueue(root, "leaf", false, nil)
tests := []struct {
name string
quota map[string]string
used map[string]string
change map[string]string
want bool
}{
{"all nil", nil, nil, nil, true},
{"nil max no usage", nil, nil, map[string]string{first: "1"}, true},
{"nil max set usage", nil, map[string]string{first: "1"}, map[string]string{second: "1"}, true},
{"max = usage other in alloc", map[string]string{first: "1"}, map[string]string{first: "1"}, map[string]string{second: "1"}, true},
{"max = usage same in alloc", map[string]string{first: "1"}, map[string]string{first: "1"}, map[string]string{first: "1"}, false},
{"usage over zero max other in alloc", map[string]string{first: "1", second: "0"}, map[string]string{second: "1"}, map[string]string{first: "1"}, true},
{"usage over zero max same in alloc", map[string]string{first: "1", second: "0"}, map[string]string{second: "1"}, map[string]string{second: "1"}, false},
{"partial fit", map[string]string{first: "2", second: "1"}, map[string]string{first: "1", second: "1"}, map[string]string{first: "1", second: "1"}, false},
{"all fit no usage", map[string]string{first: "2", second: "2"}, nil, map[string]string{first: "1", second: "1"}, true},
{"all fit", map[string]string{first: "2", second: "2"}, map[string]string{first: "1", second: "1"}, map[string]string{first: "1", second: "1"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var quota, used, change *resources.Resource
quota, err = resources.NewResourceFromConf(tt.quota)
assert.NilError(t, err, "failed to create basic resource: quota")
leaf.maxResource = quota
used, err = resources.NewResourceFromConf(tt.used)
assert.NilError(t, err, "failed to create basic resource: used")
leaf.allocatedResource = used
change, err = resources.NewResourceFromConf(tt.change)
assert.NilError(t, err, "failed to create basic resource: diff")
assert.Equal(t, leaf.allocatedResFits(change), tt.want, "allocatedResFits incorrect state returned")
})
}
}