-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
134 changes: 134 additions & 0 deletions
134
pkg/quotaplugins/quota-forest/quota-manager/quota/core/quotatree_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
Copyright 2023 The Multi-Cluster App Dispatcher Authors. | ||
Licensed 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 core | ||
|
||
import ( | ||
"testing" | ||
"unsafe" | ||
|
||
tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree" | ||
"github.com/stretchr/testify/assert" | ||
"k8s.io/utils/strings/slices" | ||
) | ||
|
||
// TestQuotaTreeAllocation : test quota tree allocation and de-allocation | ||
func TestQuotaTreeAllocation(t *testing.T) { | ||
// create a test quota tree | ||
testTreeName := "test-tree" | ||
resourceNames := []string{"CPU"} | ||
testRootNode := createTestRootNode(t) | ||
testQuotaTree := NewQuotaTree(testTreeName, testRootNode, resourceNames) | ||
assert.NotNil(t, testQuotaTree, "Expecting non-nil quota tree") | ||
|
||
// create consumers | ||
consumer1 := NewConsumer("C1", testTreeName, "B", &Allocation{x: []int{2}}, 0, 0, false) | ||
assert.NotNil(t, consumer1, "Expecting non-nil consumer1") | ||
consumer2 := NewConsumer("C2", testTreeName, "B", &Allocation{x: []int{1}}, 0, 0, false) | ||
assert.NotNil(t, consumer2, "Expecting non-nil consumer2") | ||
consumer3 := NewConsumer("C3", testTreeName, "C", &Allocation{x: []int{1}}, 0, 0, false) | ||
assert.NotNil(t, consumer3, "Expecting non-nil consumer3") | ||
consumer4P := NewConsumer("C4P", testTreeName, "B", &Allocation{x: []int{2}}, 1, 0, false) | ||
assert.NotNil(t, consumer4P, "Expecting non-nil consumer4P") | ||
consumerLarge := NewConsumer("CL", testTreeName, "C", &Allocation{x: []int{4}}, 0, 0, false) | ||
assert.NotNil(t, consumerLarge, "Expecting non-nil consumerLarge") | ||
|
||
// allocate consumers | ||
preemptedConsumers := &[]string{} | ||
|
||
// C1 -> A (does not fit on requested node B) | ||
allocated1 := testQuotaTree.Allocate(consumer1, preemptedConsumers) | ||
assert.True(t, allocated1, "Expecting consumer 1 to be allocated") | ||
node1 := consumer1.GetNode().GetID() | ||
assert.Equal(t, "A", node1, "Expecting consumer 1 to be allocated on node A") | ||
|
||
// C2 -> B | ||
allocated2 := testQuotaTree.Allocate(consumer2, preemptedConsumers) | ||
assert.True(t, allocated2, "Expecting consumer 2 to be allocated") | ||
node2 := consumer2.GetNode().GetID() | ||
assert.Equal(t, "B", node2, "Expecting consumer 2 to be allocated on node B") | ||
|
||
// C3 -> C (preempts C1 as C3 fits on its requested node C) | ||
allocated3 := testQuotaTree.Allocate(consumer3, preemptedConsumers) | ||
assert.True(t, allocated3, "Expecting consumer 3 to be allocated") | ||
node3 := consumer3.GetNode().GetID() | ||
assert.Equal(t, "C", node3, "Expecting consumer 3 to be allocated on node C") | ||
consumer1Preempted := slices.Contains(*preemptedConsumers, "C1") | ||
assert.True(t, consumer1Preempted, "Expecting consumer 1 to get preempted") | ||
|
||
// C4P -> A (high priority C4P preempts C2) | ||
allocated4P := testQuotaTree.Allocate(consumer4P, preemptedConsumers) | ||
assert.True(t, allocated4P, "Expecting consumer 4P to be allocated") | ||
node4P := consumer4P.GetNode().GetID() | ||
assert.Equal(t, "A", node4P, "Expecting consumer 4P to be allocated on node A") | ||
consumer2Preempted := slices.Contains(*preemptedConsumers, "C2") | ||
assert.True(t, consumer2Preempted, "Expecting consumer 2 to get preempted") | ||
|
||
// CL large consumer does not fit | ||
allocatedLarge := testQuotaTree.Allocate(consumerLarge, preemptedConsumers) | ||
assert.False(t, allocatedLarge, "Expecting large consumer not to be allocated") | ||
|
||
// CL -> C (large consumer allocated by force on specified node C, no preemptions) | ||
forceAllocatedLarge := testQuotaTree.ForceAllocate(consumerLarge, "C") | ||
assert.True(t, forceAllocatedLarge, "Expecting large consumer to be allocated by force") | ||
nodeLarge := consumerLarge.GetNode().GetID() | ||
assert.Equal(t, "C", nodeLarge, "Expecting large consumer to be force allocated on node C") | ||
|
||
// C3 de-allocated | ||
quotaNode3 := consumer3.GetNode() | ||
deallocated3 := testQuotaTree.DeAllocate(consumer3) | ||
assert.True(t, deallocated3, "Expecting consumer 3 to be de-allocated") | ||
node3x := consumer3.GetNode() | ||
assert.Nil(t, node3x, "Expecting consumer 3 to have a nil node") | ||
consumer3OnNode := consumerInList(consumer3, quotaNode3.GetConsumers()) | ||
assert.False(t, consumer3OnNode, "Expecting consumer 3 to be removed from its allocated node") | ||
|
||
// C3 de-allocated again | ||
deallocated3Again := testQuotaTree.DeAllocate(consumer3) | ||
assert.False(t, deallocated3Again, "Expecting non-existing consumer 3 not to be de-allocated") | ||
} | ||
|
||
// createTestRootNode : create a test quota node as a root for a tree | ||
func createTestRootNode(t *testing.T) *QuotaNode { | ||
|
||
// create three quota nodes A[2], B[1], and C[1] | ||
quotaNodeA, err := NewQuotaNode("A", &Allocation{x: []int{3}}) | ||
assert.NoError(t, err, "No error expected when creating quota node A") | ||
|
||
quotaNodeB, err := NewQuotaNode("B", &Allocation{x: []int{1}}) | ||
assert.NoError(t, err, "No error expected when creating quota node B") | ||
|
||
quotaNodeC, err := NewQuotaNode("C", &Allocation{x: []int{1}}) | ||
assert.NoError(t, err, "No error expected when creating quota node C") | ||
|
||
// connect nodes: A -> ( B C ) | ||
addedB := quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeB))) | ||
assert.True(t, addedB, "Expecting node B added as child to node A") | ||
addedC := quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeC))) | ||
assert.True(t, addedC, "Expecting node C added as child to node A") | ||
|
||
return quotaNodeA | ||
} | ||
|
||
func consumerInList(consumer *Consumer, list []*Consumer) bool { | ||
consumerID := consumer.GetID() | ||
for _, c := range list { | ||
if c.GetID() == consumerID { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
165 changes: 165 additions & 0 deletions
165
pkg/quotaplugins/quota-forest/quota-manager/quota/core/treecache_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/* | ||
Copyright 2023 The Multi-Cluster App Dispatcher Authors. | ||
Licensed 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 core | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
"unsafe" | ||
|
||
utils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils" | ||
tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
nodeSpecA string = `{ | ||
"parent": "nil", | ||
"hard": "true", | ||
"quota": { | ||
"cpu": "10", | ||
"memory": "256" | ||
} | ||
}` | ||
|
||
nodeSpecB string = `{ | ||
"parent": "A", | ||
"hard": "false", | ||
"quota": { | ||
"cpu": "2", | ||
"memory": "64" | ||
} | ||
}` | ||
|
||
nodeSpecC string = `{ | ||
"parent": "A", | ||
"quota": { | ||
"cpu": "4", | ||
"memory": "128" | ||
} | ||
}` | ||
|
||
nodeSpecD string = `{ | ||
"parent": "C", | ||
"quota": { | ||
"cpu": "2", | ||
"memory": "64" | ||
} | ||
}` | ||
|
||
treeInfo string = `{ | ||
"name": "TestTree", | ||
"resourceNames": [ | ||
"cpu", | ||
"memory" | ||
] | ||
}` | ||
|
||
nodesSpec string = `{ ` + | ||
`"A": ` + nodeSpecA + | ||
`, "B": ` + nodeSpecB + | ||
`, "C": ` + nodeSpecC + | ||
`, "D": ` + nodeSpecD + | ||
` }` | ||
) | ||
|
||
// TestTreeCacheNames : test tree cache operations on tree and resource names | ||
func TestTreeCacheNames(t *testing.T) { | ||
// create a tree cache | ||
treeCache := NewTreeCache() | ||
assert.NotNil(t, treeCache, "Expecting non-nil tree cache") | ||
|
||
// set tree name | ||
treeCache.SetDefaultTreeName() | ||
assert.Equal(t, utils.DefaultTreeName, treeCache.GetTreeName(), "Expecting default tree name") | ||
|
||
treeCache.clearTreeName() | ||
assert.Equal(t, "", treeCache.GetTreeName(), "Expecting tree name to be cleared") | ||
|
||
treeName := "test-tree" | ||
treeCache.SetTreeName(treeName) | ||
assert.Equal(t, treeName, treeCache.GetTreeName(), "Expecting tree name to be set") | ||
|
||
// set resource names | ||
treeCache.AddResourceName("cpu") | ||
treeCache.AddResourceNames([]string{"memory", "storage"}) | ||
treeCache.DeleteResourceName("storage") | ||
finalNames := []string{"cpu", "memory"} | ||
resourceNames := treeCache.GetResourceNames() | ||
assert.ElementsMatch(t, finalNames, resourceNames, "Expecting correct resource names") | ||
numResources := treeCache.GetNumResourceNames() | ||
assert.Equal(t, len(finalNames), numResources, "Expecting matching number of resource names") | ||
|
||
treeCache.clearResourceNames() | ||
resourceNames = treeCache.GetResourceNames() | ||
assert.ElementsMatch(t, []string{}, resourceNames, "Expecting empty resource names") | ||
numResources = treeCache.GetNumResourceNames() | ||
assert.Equal(t, 0, numResources, "Expecting empty resource names") | ||
|
||
treeCache.SetDefaultResourceNames() | ||
resourceNames = treeCache.GetResourceNames() | ||
finalNames = utils.DefaultResourceNames | ||
assert.ElementsMatch(t, finalNames, resourceNames, "Expecting default resource names") | ||
numResources = treeCache.GetNumResourceNames() | ||
assert.Equal(t, len(finalNames), numResources, "Expecting number of default resource names") | ||
} | ||
|
||
// TestTreeCacheNodes : test tree cache operations on nodes | ||
func TestTreeCacheNodes(t *testing.T) { | ||
// create a tree cache | ||
treeCache := NewTreeCache() | ||
assert.NotNil(t, treeCache, "Expecting non-nil tree cache") | ||
|
||
// set tree info | ||
err := treeCache.AddTreeInfoFromString(treeInfo) | ||
assert.NoError(t, err, "Expecting no error parsing tree info string") | ||
wantTreeName := "TestTree" | ||
assert.Equal(t, wantTreeName, treeCache.GetTreeName(), "Expecting tree name to be set") | ||
resourceNames := treeCache.GetResourceNames() | ||
wantResourceNames := []string{"memory", "cpu"} | ||
assert.ElementsMatch(t, wantResourceNames, resourceNames, "Expecting correct resource names") | ||
|
||
// add node specs | ||
err = treeCache.AddNodeSpecsFromString(nodesSpec) | ||
assert.NoError(t, err, "Expecting no error parsing nodes spec string") | ||
quotaTree, response := treeCache.CreateTree() | ||
testTree := createTestTree() | ||
equalTrees := reflect.DeepEqual(quotaTree, testTree) | ||
assert.True(t, equalTrees, | ||
"Expecting created tree to be same as input tree, want %v, got %v", testTree, quotaTree) | ||
assert.True(t, response.IsClean(), "Expecting clean response from tree cache") | ||
assert.ElementsMatch(t, []string{}, response.DanglingNodeNames, "Expecting no dangling nodes") | ||
|
||
} | ||
|
||
// createTestTree : create a test quota tree | ||
func createTestTree() *QuotaTree { | ||
// create quota nodes | ||
quotaNodeA, _ := NewQuotaNodeHard("A", &Allocation{x: []int{10, 256}}, true) | ||
quotaNodeB, _ := NewQuotaNodeHard("B", &Allocation{x: []int{2, 64}}, false) | ||
quotaNodeC, _ := NewQuotaNodeHard("C", &Allocation{x: []int{4, 128}}, false) | ||
quotaNodeD, _ := NewQuotaNodeHard("D", &Allocation{x: []int{2, 64}}, false) | ||
// connect nodes: A -> ( B C ( D ) ) | ||
quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeB))) | ||
quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeC))) | ||
quotaNodeC.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeD))) | ||
// make quota tree | ||
treeName := "TestTree" | ||
resourceNames := []string{"cpu", "memory"} | ||
quotaTree := NewQuotaTree(treeName, quotaNodeA, resourceNames) | ||
return quotaTree | ||
} |
Oops, something went wrong.