blob: 25b32a4a8f47365e70d68b3f82094de4bfea0e58 [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"
"testing"
"gotest.tools/v3/assert"
"github.com/apache/yunikorn-core/pkg/common/resources"
evtMock "github.com/apache/yunikorn-core/pkg/events/mock"
"github.com/apache/yunikorn-core/pkg/mock"
"github.com/apache/yunikorn-core/pkg/plugins"
"github.com/apache/yunikorn-scheduler-interface/lib/go/common"
"github.com/apache/yunikorn-scheduler-interface/lib/go/si"
)
const testNode = "testnode"
func TestNewNode(t *testing.T) {
// simple nil check
node := NewNode(nil)
if node != nil {
t.Error("node not returned correctly: node is nul or incorrect name")
}
proto := newProto(testNode, nil, nil, nil)
node = NewNode(proto)
if node == nil || node.NodeID != testNode {
t.Error("node not returned correctly: node is nul or incorrect name")
}
totalRes := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 100, "second": 100})
proto = newProto(testNode, totalRes, nil, map[string]string{})
node = NewNode(proto)
if node == nil || node.NodeID != testNode {
t.Fatal("node not returned correctly: node is nul or incorrect name")
}
if !resources.Equals(node.totalResource, totalRes) ||
!resources.Equals(node.availableResource, totalRes) {
t.Errorf("node resources not set correctly: %v expected got %v and %v",
totalRes, node.totalResource, node.availableResource)
}
assert.Equal(t, node.Partition, "")
// set special attributes and get a new node
proto.Attributes = map[string]string{
common.HostName: "host1",
common.RackName: "rack1",
common.NodePartition: "partition1",
}
node = NewNode(proto)
if node == nil || node.NodeID != testNode {
t.Fatal("node not returned correctly: node is nul or incorrect name")
}
assert.Equal(t, "host1", node.Hostname)
assert.Equal(t, "rack1", node.Rackname)
assert.Equal(t, "partition1", node.Partition)
// test capacity/available/occupied resources
totalResources := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 100, "second": 100})
occupiedResources := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 30, "second": 20})
availableResources := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 70, "second": 80})
proto = newProto(testNode, totalResources, occupiedResources, map[string]string{})
node = NewNode(proto)
assert.Equal(t, node.NodeID, testNode, "node not returned correctly: node is nul or incorrect name")
if !resources.Equals(node.GetCapacity(), totalResources) {
t.Errorf("node total resources not set correctly: %v expected got %v",
totalResources, node.GetCapacity())
}
if !resources.Equals(node.GetAvailableResource(), availableResources) {
t.Errorf("node available resources not set correctly: %v expected got %v",
availableResources, node.GetAvailableResource())
}
if !resources.Equals(node.GetOccupiedResource(), occupiedResources) {
t.Errorf("node occupied resources not set correctly: %v expected got %v",
occupiedResources, node.GetOccupiedResource())
}
}
func TestCheckConditions(t *testing.T) {
node := newNode(nodeID1, map[string]resources.Quantity{"first": 100, "second": 100})
if node == nil || node.NodeID != nodeID1 {
t.Fatalf("node create failed which should not have %v", node)
}
// Check if we can allocate on scheduling node (no plugins)
res := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1})
ask := newAllocationAsk("test", "app001", res)
if !node.preAllocateConditions(ask) {
t.Error("node with scheduling set to true no plugins should allow allocation")
}
// TODO add mock for plugin to extend tests
}
func TestPreAllocateCheck(t *testing.T) {
nodeID := nodeID1
resNode := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "second": 1})
node := newNode(nodeID, resNode.Resources)
if node == nil || node.NodeID != nodeID {
t.Fatalf("node create failed which should not have %v", node)
}
// special cases
if node.preAllocateCheck(nil, "") {
t.Errorf("nil resource should not have fitted on node")
}
resNeg := resources.NewResourceFromMap(map[string]resources.Quantity{"first": -1})
if node.preAllocateCheck(resNeg, "") {
t.Errorf("negative resource should not have fitted on node")
}
// Check if we can allocate on scheduling node
resSmall := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5})
resLarge := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 15})
assert.Assert(t, node.preAllocateCheck(resNode, ""), "node resource should have fitted on node")
assert.Assert(t, node.preAllocateCheck(resSmall, ""), "small resource should have fitted on node")
assert.Assert(t, !node.preAllocateCheck(resLarge, ""), "too large resource should not have fitted on node")
// unknown resource type in request is rejected always
resOther := resources.NewResourceFromMap(map[string]resources.Quantity{"unknown": 1})
assert.Assert(t, !node.preAllocateCheck(resOther, ""), "unknown resource type should not have fitted on node")
// set allocated resource
alloc := newAllocation(appID1, nodeID, resSmall)
node.AddAllocation(alloc)
assert.Assert(t, node.preAllocateCheck(resSmall, ""), "small resource should have fitted in available allocation")
assert.Assert(t, !node.preAllocateCheck(resNode, ""), "node resource should not have fitted in available allocation")
// check if we can allocate on a reserved node
q := map[string]resources.Quantity{"first": 0}
res := resources.NewResourceFromMap(q)
ask := newAllocationAsk(aKey, appID1, res)
app := newApplication(appID1, "default", "root.unknown")
// standalone reservation unreserve returns false as app is not reserved
reserve := newReservation(node, app, ask, false)
node.reservations[reserve.getKey()] = reserve
assert.Assert(t, !node.preAllocateCheck(resSmall, "app-2"), "node was reserved for different app but check passed")
assert.Assert(t, !node.preAllocateCheck(resSmall, "app-1|alloc-2"), "node was reserved for this app but not the alloc and check passed")
assert.Assert(t, node.preAllocateCheck(resSmall, appID1), "node was reserved for this app but check did not pass check")
assert.Assert(t, node.preAllocateCheck(resSmall, "app-1|alloc-1"), "node was reserved for this app/alloc but check did not pass check")
// Check if we can allocate on non scheduling node
node.SetSchedulable(false)
assert.Assert(t, !node.preAllocateCheck(resSmall, ""), "node with scheduling set to false should not allow allocation")
}
// Only test the CanAllocate code, the used logic in preAllocateCheck has its own test
func TestCanAllocate(t *testing.T) {
available := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "second": 10})
request := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1, "second": 1})
other := resources.NewResourceFromMap(map[string]resources.Quantity{"unknown": 1})
tests := []struct {
name string
available *resources.Resource
request *resources.Resource
want bool
}{
{"all nils", nil, nil, true},
{"nil node available", nil, request, false},
{"all matching, req smaller", available, request, true},
{"partial match request", available, resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5}), true},
{"all matching, req larger", available, resources.Add(request, available), false},
{"partial match available", available, resources.Add(request, other), false},
{"unmatched request", available, other, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sn := &Node{
availableResource: tt.available,
}
assert.Equal(t, sn.CanAllocate(tt.request), tt.want, "unexpected node can run result")
})
}
}
func TestNodeReservation(t *testing.T) {
node := newNode(nodeID1, map[string]resources.Quantity{"first": 10})
if node == nil || node.NodeID != nodeID1 {
t.Fatalf("node create failed which should not have %v", node)
}
if node.IsReserved() {
t.Fatal("new node should not have reservations")
}
if node.isReservedForApp("") {
t.Error("new node should not have reservations for empty key")
}
if node.isReservedForApp("unknown") {
t.Error("new node should not have reservations for unknown key")
}
// reserve illegal request
err := node.Reserve(nil, nil)
if err == nil {
t.Errorf("illegal reservation requested but did not fail: error %v", err)
}
// too large for node
res := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 15})
ask := newAllocationAsk(aKey, appID1, res)
app := newApplication(appID1, "default", "root.unknown")
err = node.Reserve(app, ask)
if err == nil {
t.Errorf("requested reservation does not fit in node resource but did not fail: error %v", err)
}
// resource type not available on node
res = resources.NewResourceFromMap(map[string]resources.Quantity{"unknown": 1})
ask = newAllocationAsk(aKey, appID1, res)
app = newApplication(appID1, "default", "root.unknown")
err = node.Reserve(app, ask)
if err == nil {
t.Errorf("requested reservation does not match node resource types but did not fail: error %v", err)
}
res = resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5})
ask = newAllocationAsk(aKey, appID1, res)
app = newApplication(appID1, "default", "root.unknown")
// reserve that works
err = node.Reserve(app, ask)
assert.NilError(t, err, "reservation should not have failed")
if node.isReservedForApp("") {
t.Error("node should not have reservations for empty key")
}
if node.isReservedForApp("unknown") {
t.Errorf("node should not have reservations for unknown key")
}
if node.IsReserved() && !node.isReservedForApp(appID1) {
t.Errorf("node should have reservations for app-1")
}
// 2nd reservation on node
err = node.Reserve(nil, nil)
if err == nil {
t.Errorf("reservation requested on already reserved node: error %v", err)
}
// unreserve different app
_, err = node.unReserve(nil, nil)
if err == nil {
t.Errorf("illegal reservation release but did not fail: error %v", err)
}
ask2 := newAllocationAsk("alloc-2", appID2, res)
app2 := newApplication(appID2, "default", "root.unknown")
var num int
num, err = node.unReserve(app2, ask2)
assert.NilError(t, err, "un-reserve different app should have failed without error")
assert.Equal(t, num, 0, "un-reserve different app should have failed without releases")
num, err = node.unReserve(app, ask)
assert.NilError(t, err, "un-reserve should not have failed")
assert.Equal(t, num, 1, "un-reserve app should have released ")
}
func TestIsReservedForApp(t *testing.T) {
node := newNode(nodeID1, map[string]resources.Quantity{"first": 10})
if node == nil || node.NodeID != nodeID1 {
t.Fatalf("node create failed which should not have %v", node)
}
if node.IsReserved() {
t.Fatal("new node should not have reservations")
}
// check if we can allocate on a reserved node
q := map[string]resources.Quantity{"first": 0}
res := resources.NewResourceFromMap(q)
ask := newAllocationAsk(aKey, appID1, res)
app := newApplication(appID1, "default", "root.unknown")
// standalone reservation unreserve returns false as app is not reserved
reserve := newReservation(node, app, ask, false)
node.reservations[reserve.getKey()] = reserve
if node.isReservedForApp("app-2") {
t.Error("node was reserved for different app but check passed ")
}
if node.isReservedForApp("app-1|alloc-2") {
t.Error("node was reserved for this app but not the alloc and check passed ")
}
if !node.isReservedForApp(appID1) {
t.Error("node was reserved for this app but check did not passed ")
}
if !node.isReservedForApp("app-1|alloc-1") {
t.Error("node was reserved for this app/alloc but check did not passed ")
}
// app name similarity check: chop of the last char to make sure we check the full name
similar := appID1[:len(appID1)-1]
if node.isReservedForApp(similar) {
t.Errorf("similar app should not have reservations on node %s", similar)
}
}
func TestAttributes(t *testing.T) {
type outputFormat struct {
hostname, rackname, partition string
attribites map[string]string
}
attribitesOfNode1 := map[string]string{common.NodePartition: "partition1", "something": "just a text"}
attribitesOfNode2 := map[string]string{common.HostName: "test", common.NodePartition: "partition2", "disk": "SSD", "GPU-type": "3090"}
var tests = []struct {
inputs map[string]string
expected outputFormat
}{
{
attribitesOfNode1,
outputFormat{"", "", "partition1", attribitesOfNode1},
},
{
attribitesOfNode2,
outputFormat{"test", "", "partition2", attribitesOfNode2},
},
}
for index, tt := range tests {
testname := fmt.Sprintf("Attributes in the node %d", index)
t.Run(testname, func(t *testing.T) {
nodename := fmt.Sprintf("%s-%d", testNode, index)
node := NewNode(newProto(nodename, nil, nil, tt.inputs))
if node == nil || node.NodeID != nodename {
t.Error("node not returned correctly: node is nul or incorrect name")
}
if got, expect := node.Hostname, tt.expected.hostname; got != expect {
t.Errorf("node hostname: got %s, expected %s", got, expect)
}
if got, expect := node.Rackname, tt.expected.rackname; got != expect {
t.Errorf("node rackname: got %s, expected %s", got, expect)
}
if got, expect := node.Partition, tt.expected.partition; got != expect {
t.Errorf("node partition: got %s, expected %s", got, expect)
}
attribites := node.GetAttributes()
if got, expect := len(attribites), len(tt.expected.attribites); got != expect {
t.Errorf("length of attributes: got %d, expected %d", got, expect)
}
for key, expect := range tt.expected.attribites {
if got := node.GetAttribute(key); got != expect {
t.Errorf("Attribute %s: got %s, expect %s", key, got, expect)
}
if got := attribites[key]; got != expect {
t.Errorf("Attribute %s: got %s, expect %s", key, got, expect)
}
}
})
}
}
func TestGetInstanceType(t *testing.T) {
proto := newProto(testNode, nil, nil, map[string]string{
common.NodePartition: "partition1",
"label1": "key1",
"label2": "key2",
common.InstanceType: "HighMem",
})
node := NewNode(proto)
if node == nil || node.NodeID != testNode {
t.Fatal("node not returned correctly: node is nul or incorrect name")
}
assert.Equal(t, "", node.Hostname)
assert.Equal(t, "", node.Rackname)
assert.Equal(t, "partition1", node.Partition)
value := node.GetInstanceType()
assert.Equal(t, "HighMem", value, "node instanceType not set, expected 'HighMem' got '%v'", value)
}
func TestAddAllocation(t *testing.T) {
node := newNode("node-123", map[string]resources.Quantity{"first": 100, "second": 200})
if !resources.IsZero(node.GetAllocatedResource()) {
t.Fatal("Failed to initialize resource")
}
// check nil alloc
assert.Assert(t, !node.AddAllocation(nil), "nil allocation should not have been added: %v", node)
// check alloc that does not match
unknown := resources.NewResourceFromMap(map[string]resources.Quantity{"unknown": 1})
alloc := newAllocation(appID1, nodeID1, unknown)
assert.Assert(t, !node.AddAllocation(alloc), "unmatched resource type in allocation should not have been added: %v", node)
// allocate half of the resources available and check the calculation
half := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 50, "second": 100})
alloc = newAllocation(appID1, nodeID1, half)
assert.Assert(t, node.AddAllocation(alloc), "add allocation 1 should not have failed")
if node.GetAllocation(alloc.GetAllocationKey()) == nil {
t.Fatal("failed to add allocations: allocation not returned")
}
if !resources.Equals(node.GetAllocatedResource(), half) {
t.Errorf("failed to add allocations expected %v, got %v", half, node.GetAllocatedResource())
}
if !resources.Equals(node.GetAvailableResource(), half) {
t.Errorf("failed to update available resources expected %v, got %v", half, node.GetAvailableResource())
}
expectedUtilizedResource := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 50, "second": 50})
if !resources.Equals(node.GetUtilizedResource(), expectedUtilizedResource) {
t.Errorf("failed to get utilized resources expected %v, got %v", expectedUtilizedResource, node.GetUtilizedResource())
}
// second and check calculation
piece := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 25, "second": 50})
alloc = newAllocation(appID1, nodeID1, piece)
assert.Assert(t, node.AddAllocation(alloc), "add allocation 2 should not have failed")
if node.GetAllocation(alloc.GetAllocationKey()) == nil {
t.Fatal("failed to add allocations: allocation not returned")
}
piece.AddTo(half)
if !resources.Equals(node.GetAllocatedResource(), piece) {
t.Errorf("failed to add allocations expected %v, got %v", piece, node.GetAllocatedResource())
}
left := resources.Sub(node.GetCapacity(), piece)
if !resources.Equals(node.GetAvailableResource(), left) {
t.Errorf("failed to update available resources expected %v, got %v", left, node.GetAvailableResource())
}
expectedUtilizedResource1 := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 75, "second": 75})
if !resources.Equals(node.GetUtilizedResource(), expectedUtilizedResource1) {
t.Errorf("failed to get utilized resources expected %v, got %v", expectedUtilizedResource1, node.GetUtilizedResource())
}
}
func TestRemoveAllocation(t *testing.T) {
node := newNode("node-123", map[string]resources.Quantity{"first": 100, "second": 200})
if !resources.IsZero(node.GetAllocatedResource()) {
t.Fatal("Failed to initialize resource")
}
// allocate half of the resources available and check the calculation
half := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 50, "second": 100})
alloc1 := newAllocation(appID1, nodeID1, half)
node.AddAllocation(alloc1)
if node.GetAllocation(alloc1.GetAllocationKey()) == nil {
t.Fatal("failed to add allocations: allocation not returned")
}
// check empty alloc
if node.RemoveAllocation("") != nil {
t.Errorf("empty allocation should not have been removed: %v", node)
}
if node.RemoveAllocation("not exist") != nil {
t.Errorf("'not exist' allocation should not have been removed: %v", node)
}
if !resources.Equals(node.GetAllocatedResource(), half) {
t.Errorf("allocated resource not set correctly %v got %v", half, node.GetAllocatedResource())
}
// add second alloc and remove first check calculation
piece := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 25, "second": 50})
alloc2 := newAllocation(appID1, nodeID1, piece)
node.AddAllocation(alloc2)
if node.GetAllocation(alloc2.GetAllocationKey()) == nil {
t.Fatal("failed to add allocations: allocation not returned")
}
alloc := node.RemoveAllocation(alloc1.GetAllocationKey())
if alloc == nil {
t.Error("allocation should have been removed but was not")
}
if !resources.Equals(node.GetAllocatedResource(), piece) {
t.Errorf("allocated resource not set correctly %v got %v", piece, node.GetAllocatedResource())
}
left := resources.Sub(node.GetCapacity(), piece)
if !resources.Equals(node.GetAvailableResource(), left) {
t.Errorf("allocated resource not set correctly %v got %v", left, node.GetAvailableResource())
}
expectedUtilizedResource := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 25, "second": 25})
if !resources.Equals(node.GetUtilizedResource(), expectedUtilizedResource) {
t.Errorf("failed to get utilized resources expected %v, got %v", expectedUtilizedResource, node.GetUtilizedResource())
}
}
func TestNodeReplaceAllocation(t *testing.T) {
node := newNode("node-123", map[string]resources.Quantity{"first": 100, "second": 200})
assert.Assert(t, resources.IsZero(node.GetAllocatedResource()), "failed to initialize node")
// allocate half of the resources available and check the calculation
half := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 50, "second": 100})
ph := newPlaceholderAlloc(appID1, nodeID1, half)
node.AddAllocation(ph)
assert.Assert(t, node.GetAllocation(ph.GetAllocationKey()) != nil, "failed to add placeholder allocation")
assert.Assert(t, resources.Equals(node.GetAllocatedResource(), half), "allocated resource not set correctly %v got %v", half, node.GetAllocatedResource())
assert.Assert(t, resources.Equals(node.GetAvailableResource(), half), "available resource not set correctly %v got %v", half, node.GetAvailableResource())
piece := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 25, "second": 50})
alloc := newAllocation(appID1, nodeID1, piece)
// calculate the delta: new allocation resource - placeholder (should be negative!)
delta := resources.Sub(piece, half)
assert.Assert(t, delta.HasNegativeValue(), "expected negative values in delta")
// swap and check the calculation
node.ReplaceAllocation(ph.GetAllocationKey(), alloc, delta)
assert.Assert(t, node.GetAllocation(alloc.GetAllocationKey()) != nil, "failed to replace allocation: allocation not returned")
assert.Assert(t, resources.Equals(node.GetAllocatedResource(), piece), "allocated resource not set correctly %v got %v", piece, node.GetAllocatedResource())
assert.Assert(t, resources.Equals(node.GetAvailableResource(), resources.Sub(node.GetCapacity(), piece)), "available resource not set correctly %v got %v", resources.Sub(node.GetCapacity(), piece), node.GetAvailableResource())
// clean up all should be zero
assert.Assert(t, node.RemoveAllocation(alloc.GetAllocationKey()) != nil, "allocation should have been removed but was not")
assert.Assert(t, resources.IsZero(node.GetAllocatedResource()), "allocated resource not updated correctly")
assert.Assert(t, resources.Equals(node.GetAvailableResource(), node.GetCapacity()), "available resource not set correctly %v got %v", node.GetCapacity(), node.GetAvailableResource())
}
func TestGetAllocation(t *testing.T) {
node := newNode("node-123", map[string]resources.Quantity{"first": 100, "second": 200})
if !resources.IsZero(node.GetAllocatedResource()) {
t.Fatal("Failed to initialize resource")
}
// nothing allocated get a nil
alloc := node.GetAllocation("")
if alloc != nil {
t.Fatalf("allocation should not have been found")
}
alloc = newAllocation(appID1, nodeID1, nil)
node.AddAllocation(alloc)
alloc = node.GetAllocation(alloc.GetAllocationKey())
if alloc == nil {
t.Fatalf("allocation should have been found")
}
// unknown allocation get a nil
alloc = node.GetAllocation("fake")
if alloc != nil {
t.Fatalf("allocation should not have been found (fake key)")
}
}
func TestGetAllocations(t *testing.T) {
node := newNode("node-123", map[string]resources.Quantity{"first": 100, "second": 200})
if !resources.IsZero(node.GetAllocatedResource()) {
t.Fatal("Failed to initialize resource")
}
// nothing allocated get an empty list
allocs := node.GetAllAllocations()
if allocs == nil || len(allocs) != 0 {
t.Fatalf("allocation length should be 0 on new node")
}
alloc1 := newAllocation(appID1, nodeID1, nil)
alloc2 := newAllocation(appID1, nodeID1, nil)
// allocate
node.AddAllocation(alloc1)
node.AddAllocation(alloc2)
assert.Equal(t, 2, len(node.GetAllAllocations()), "allocation length mismatch")
// This should not happen in real code just making sure the code does do what is expected
node.AddAllocation(alloc2)
assert.Equal(t, 2, len(node.GetAllAllocations()), "allocation length mismatch")
}
func TestSchedulingState(t *testing.T) {
node := newNode("node-123", nil)
if !node.IsSchedulable() {
t.Error("failed to initialize node: not schedulable")
}
node.SetSchedulable(false)
if node.IsSchedulable() {
t.Error("failed to modify node state: schedulable")
}
}
func TestUpdateResources(t *testing.T) {
total := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "second": 10})
node := newNodeRes("node-123", total)
if !resources.IsZero(node.occupiedResource) || !resources.IsZero(node.allocatedResource) || !resources.Equals(total, node.GetCapacity()) {
t.Fatalf("node not initialised correctly")
}
occupied := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1, "second": 1})
node.SetOccupiedResource(occupied)
if !resources.Equals(occupied, node.GetOccupiedResource()) {
t.Errorf("occupied resources should have been updated to: %s, got %s", occupied, node.GetOccupiedResource())
}
available := resources.Sub(total, occupied)
if !resources.Equals(available, node.GetAvailableResource()) {
t.Errorf("available resources should have been updated to: %s, got %s", available, node.GetAvailableResource())
}
// adjust capacity check available updated
total = resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5, "second": 5})
node.SetCapacity(total)
if !resources.Equals(total, node.GetCapacity()) {
t.Errorf("total resources should have been updated to: %s, got %s", total, node.GetCapacity())
}
available = resources.Sub(total, occupied)
if !resources.Equals(available, node.GetAvailableResource()) {
t.Errorf("available resources should have been updated to: %s, got %s", available, node.GetAvailableResource())
}
// over allocate available should go negative and no error
occupied = resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2, "second": 10})
node.SetOccupiedResource(occupied)
if !resources.Equals(occupied, node.GetOccupiedResource()) {
t.Errorf("occupied resources should have been updated to: %s, got %s", occupied, node.GetOccupiedResource())
}
available = resources.Sub(total, occupied)
if !resources.Equals(available, node.GetAvailableResource()) {
t.Errorf("available resources should have been updated to: %s, got %s", available, node.GetAvailableResource())
}
// reset and check with allocated
total = resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "second": 10})
node = newNodeRes("node-123", total)
if !resources.IsZero(node.occupiedResource) || !resources.IsZero(node.allocatedResource) || !resources.Equals(total, node.GetCapacity()) {
t.Fatalf("node not initialised correctly")
}
alloc := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5, "second": 5})
node.allocatedResource = alloc.Clone()
available = resources.Sub(total, alloc)
// fake the update to recalculate available
node.refreshAvailableResource()
if !resources.Equals(available, node.GetAvailableResource()) {
t.Errorf("available resources should have been updated to: %s, got %s", available, node.GetAvailableResource())
}
// set occupied and make sure available changes
occupied = resources.NewResourceFromMap(map[string]resources.Quantity{"first": 3, "second": 1})
node.SetOccupiedResource(occupied)
if !resources.Equals(occupied, node.GetOccupiedResource()) {
t.Errorf("occupied resources should have been updated to: %s, got %s", occupied, node.GetOccupiedResource())
}
available.SubFrom(occupied)
if !resources.Equals(available, node.GetAvailableResource()) {
t.Errorf("available resources should have been updated to: %s, got %s", available, node.GetAvailableResource())
}
}
type testListener struct {
updateCount int
}
func (tl *testListener) NodeUpdated(_ *Node) {
tl.updateCount++
}
func TestAddRemoveListener(t *testing.T) {
tl := testListener{}
total := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "second": 10})
node := newNodeRes("node-123", total)
node.AddListener(&tl)
assert.Equal(t, 0, tl.updateCount, "listener should not have fired")
node.SetSchedulable(false)
assert.Equal(t, 1, tl.updateCount, "listener should have fired once")
node.RemoveListener(&tl)
node.SetSchedulable(true)
assert.Equal(t, 1, tl.updateCount, "listener should not have fired again")
}
func TestNodeEvents(t *testing.T) {
mockEvents := evtMock.NewEventSystem()
total := resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 100, "memory": 100})
occupied := resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 10, "memory": 10})
proto := newProto(testNode, total, occupied, map[string]string{
"ready": "true",
})
node := NewNode(proto)
node.nodeEvents = newNodeEvents(node, mockEvents)
node.SendNodeAddedEvent()
assert.Equal(t, 1, len(mockEvents.Events))
event := mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_ADD, event.EventChangeType)
mockEvents.Reset()
node.SendNodeRemovedEvent()
assert.Equal(t, 1, len(mockEvents.Events))
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_REMOVE, event.EventChangeType)
mockEvents.Reset()
node.AddAllocation(&Allocation{
allocatedResource: resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 1, "memory": 1}),
allocationKey: aKey,
})
assert.Equal(t, 1, len(mockEvents.Events))
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_ADD, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_ALLOC, event.EventChangeDetail)
mockEvents.Reset()
node.RemoveAllocation(aKey)
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_REMOVE, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_ALLOC, event.EventChangeDetail)
mockEvents.Reset()
node.SetOccupiedResource(resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 20, "memory": 20}))
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_SET, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_OCCUPIED, event.EventChangeDetail)
assert.Equal(t, int64(20), event.Resource.Resources["cpu"].Value)
assert.Equal(t, int64(20), event.Resource.Resources["memory"].Value)
mockEvents.Reset()
node.SetCapacity(resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 90, "memory": 90}))
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_SET, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_CAPACITY, event.EventChangeDetail)
assert.Equal(t, int64(90), event.Resource.Resources["cpu"].Value)
assert.Equal(t, int64(90), event.Resource.Resources["memory"].Value)
mockEvents.Reset()
node.SetSchedulable(false)
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_SET, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_SCHEDULABLE, event.EventChangeDetail)
mockEvents.Reset()
res := resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 10})
ask := newAllocationAsk(aKey, appID1, res)
app := newApplication(appID1, "default", "root.unknown")
err := node.Reserve(app, ask)
assert.NilError(t, err, "could not reserve")
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_ADD, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_RESERVATION, event.EventChangeDetail)
mockEvents.Reset()
_, err = node.unReserve(app, ask)
assert.NilError(t, err, "could not unreserve")
event = mockEvents.Events[0]
assert.Equal(t, si.EventRecord_NODE, event.Type)
assert.Equal(t, si.EventRecord_REMOVE, event.EventChangeType)
assert.Equal(t, si.EventRecord_NODE_RESERVATION, event.EventChangeDetail)
}
func TestNode_FitInNode(t *testing.T) {
total := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10, "second": 10})
request := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1, "second": 1})
other := resources.NewResourceFromMap(map[string]resources.Quantity{"unknown": 1})
tests := []struct {
name string
totalRes *resources.Resource
resRequest *resources.Resource
want bool
}{
{"all nils", nil, nil, true},
{"nil node size", nil, request, false},
{"all matching, req smaller", total, request, true},
{"partial match request", total, resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5}), true},
{"all matching, req larger", total, resources.Add(request, total), false},
{"partial match total", total, resources.Add(request, other), false},
{"unmatched request", total, other, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sn := &Node{
totalResource: tt.totalRes,
}
assert.Equal(t, sn.FitInNode(tt.resRequest), tt.want, "unexpected node fit result")
})
}
}
func TestPreconditions(t *testing.T) {
current := plugins.GetResourceManagerCallbackPlugin()
defer func() {
plugins.RegisterSchedulerPlugin(current)
}()
plugins.RegisterSchedulerPlugin(mock.NewPredicatePlugin(true, map[string]int{}))
total := resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 100, "memory": 100})
occupied := resources.NewResourceFromMap(map[string]resources.Quantity{"cpu": 10, "memory": 10})
proto := newProto(testNode, total, occupied, map[string]string{
"ready": "true",
})
res := resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1})
ask := newAllocationAsk("test", "app001", res)
eventSystem := evtMock.NewEventSystem()
ask.askEvents = newAskEvents(ask, eventSystem)
node := NewNode(proto)
// failure
node.preConditions(ask, true)
assert.Equal(t, 1, len(eventSystem.Events))
assert.Equal(t, "Predicate failed for request 'test' with message: 'fake predicate plugin failed'", eventSystem.Events[0].Message)
assert.Equal(t, 1, len(ask.allocLog))
assert.Equal(t, "fake predicate plugin failed", ask.allocLog["fake predicate plugin failed"].Message)
// pass
eventSystem.Reset()
plugins.RegisterSchedulerPlugin(mock.NewPredicatePlugin(false, map[string]int{}))
node.preConditions(ask, true)
assert.Equal(t, 0, len(eventSystem.Events))
assert.Equal(t, 1, len(ask.allocLog))
}