Skip to content

Commit 9f82346

Browse files
committed
added quota tree unit tests 3
1 parent 3ae897d commit 9f82346

File tree

3 files changed

+373
-0
lines changed

3 files changed

+373
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
Copyright 2023 The Multi-Cluster App Dispatcher Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package core
18+
19+
import (
20+
"testing"
21+
"unsafe"
22+
23+
tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree"
24+
"github.com/stretchr/testify/assert"
25+
"k8s.io/utils/strings/slices"
26+
)
27+
28+
// TestQuotaTreeAllocation : test quota tree allocation and de-allocation
29+
func TestQuotaTreeAllocation(t *testing.T) {
30+
// create a test quota tree
31+
testTreeName := "test-tree"
32+
resourceNames := []string{"CPU"}
33+
testRootNode := createTestRootNode(t)
34+
testQuotaTree := NewQuotaTree(testTreeName, testRootNode, resourceNames)
35+
assert.NotNil(t, testQuotaTree, "Expecting non-nil quota tree")
36+
37+
// create consumers
38+
consumer1 := NewConsumer("C1", testTreeName, "B", &Allocation{x: []int{2}}, 0, 0, false)
39+
assert.NotNil(t, consumer1, "Expecting non-nil consumer1")
40+
consumer2 := NewConsumer("C2", testTreeName, "B", &Allocation{x: []int{1}}, 0, 0, false)
41+
assert.NotNil(t, consumer2, "Expecting non-nil consumer2")
42+
consumer3 := NewConsumer("C3", testTreeName, "C", &Allocation{x: []int{1}}, 0, 0, false)
43+
assert.NotNil(t, consumer3, "Expecting non-nil consumer3")
44+
consumer4P := NewConsumer("C4P", testTreeName, "B", &Allocation{x: []int{2}}, 1, 0, false)
45+
assert.NotNil(t, consumer4P, "Expecting non-nil consumer4P")
46+
consumerLarge := NewConsumer("CL", testTreeName, "C", &Allocation{x: []int{4}}, 0, 0, false)
47+
assert.NotNil(t, consumerLarge, "Expecting non-nil consumerLarge")
48+
49+
// allocate consumers
50+
preemptedConsumers := &[]string{}
51+
52+
// C1 -> A (does not fit on requested node B)
53+
allocated1 := testQuotaTree.Allocate(consumer1, preemptedConsumers)
54+
assert.True(t, allocated1, "Expecting consumer 1 to be allocated")
55+
node1 := consumer1.GetNode().GetID()
56+
assert.Equal(t, "A", node1, "Expecting consumer 1 to be allocated on node A")
57+
58+
// C2 -> B
59+
allocated2 := testQuotaTree.Allocate(consumer2, preemptedConsumers)
60+
assert.True(t, allocated2, "Expecting consumer 2 to be allocated")
61+
node2 := consumer2.GetNode().GetID()
62+
assert.Equal(t, "B", node2, "Expecting consumer 2 to be allocated on node B")
63+
64+
// C3 -> C (preempts C1 as C3 fits on its requested node C)
65+
allocated3 := testQuotaTree.Allocate(consumer3, preemptedConsumers)
66+
assert.True(t, allocated3, "Expecting consumer 3 to be allocated")
67+
node3 := consumer3.GetNode().GetID()
68+
assert.Equal(t, "C", node3, "Expecting consumer 3 to be allocated on node C")
69+
consumer1Preempted := slices.Contains(*preemptedConsumers, "C1")
70+
assert.True(t, consumer1Preempted, "Expecting consumer 1 to get preempted")
71+
72+
// C4P -> A (high priority C4P preempts C2)
73+
allocated4P := testQuotaTree.Allocate(consumer4P, preemptedConsumers)
74+
assert.True(t, allocated4P, "Expecting consumer 4P to be allocated")
75+
node4P := consumer4P.GetNode().GetID()
76+
assert.Equal(t, "A", node4P, "Expecting consumer 4P to be allocated on node A")
77+
consumer2Preempted := slices.Contains(*preemptedConsumers, "C2")
78+
assert.True(t, consumer2Preempted, "Expecting consumer 2 to get preempted")
79+
80+
// CL large consumer does not fit
81+
allocatedLarge := testQuotaTree.Allocate(consumerLarge, preemptedConsumers)
82+
assert.False(t, allocatedLarge, "Expecting large consumer not to be allocated")
83+
84+
// CL -> C (large consumer allocated by force on specified node C, no preemptions)
85+
forceAllocatedLarge := testQuotaTree.ForceAllocate(consumerLarge, "C")
86+
assert.True(t, forceAllocatedLarge, "Expecting large consumer to be allocated by force")
87+
nodeLarge := consumerLarge.GetNode().GetID()
88+
assert.Equal(t, "C", nodeLarge, "Expecting large consumer to be force allocated on node C")
89+
90+
// C3 de-allocated
91+
quotaNode3 := consumer3.GetNode()
92+
deallocated3 := testQuotaTree.DeAllocate(consumer3)
93+
assert.True(t, deallocated3, "Expecting consumer 3 to be de-allocated")
94+
node3x := consumer3.GetNode()
95+
assert.Nil(t, node3x, "Expecting consumer 3 to have a nil node")
96+
consumer3OnNode := consumerInList(consumer3, quotaNode3.GetConsumers())
97+
assert.False(t, consumer3OnNode, "Expecting consumer 3 to be removed from its allocated node")
98+
99+
// C3 de-allocated again
100+
deallocated3Again := testQuotaTree.DeAllocate(consumer3)
101+
assert.False(t, deallocated3Again, "Expecting non-existing consumer 3 not to be de-allocated")
102+
}
103+
104+
// createTestRootNode : create a test quota node as a root for a tree
105+
func createTestRootNode(t *testing.T) *QuotaNode {
106+
107+
// create three quota nodes A[2], B[1], and C[1]
108+
quotaNodeA, err := NewQuotaNode("A", &Allocation{x: []int{3}})
109+
assert.NoError(t, err, "No error expected when creating quota node A")
110+
111+
quotaNodeB, err := NewQuotaNode("B", &Allocation{x: []int{1}})
112+
assert.NoError(t, err, "No error expected when creating quota node B")
113+
114+
quotaNodeC, err := NewQuotaNode("C", &Allocation{x: []int{1}})
115+
assert.NoError(t, err, "No error expected when creating quota node C")
116+
117+
// connect nodes: A -> ( B C )
118+
addedB := quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeB)))
119+
assert.True(t, addedB, "Expecting node B added as child to node A")
120+
addedC := quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeC)))
121+
assert.True(t, addedC, "Expecting node C added as child to node A")
122+
123+
return quotaNodeA
124+
}
125+
126+
func consumerInList(consumer *Consumer, list []*Consumer) bool {
127+
consumerID := consumer.GetID()
128+
for _, c := range list {
129+
if c.GetID() == consumerID {
130+
return true
131+
}
132+
}
133+
return false
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright 2023 The Multi-Cluster App Dispatcher Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package core
18+
19+
import (
20+
"reflect"
21+
"testing"
22+
"unsafe"
23+
24+
utils "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/quota/utils"
25+
tree "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/quotaplugins/quota-forest/quota-manager/tree"
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
var (
30+
nodeSpecA string = `{
31+
"parent": "nil",
32+
"hard": "true",
33+
"quota": {
34+
"cpu": "10",
35+
"memory": "256"
36+
}
37+
}`
38+
39+
nodeSpecB string = `{
40+
"parent": "A",
41+
"hard": "false",
42+
"quota": {
43+
"cpu": "2",
44+
"memory": "64"
45+
}
46+
}`
47+
48+
nodeSpecC string = `{
49+
"parent": "A",
50+
"quota": {
51+
"cpu": "4",
52+
"memory": "128"
53+
}
54+
}`
55+
56+
nodeSpecD string = `{
57+
"parent": "C",
58+
"quota": {
59+
"cpu": "2",
60+
"memory": "64"
61+
}
62+
}`
63+
64+
treeInfo string = `{
65+
"name": "TestTree",
66+
"resourceNames": [
67+
"cpu",
68+
"memory"
69+
]
70+
}`
71+
72+
nodesSpec string = `{ ` +
73+
`"A": ` + nodeSpecA +
74+
`, "B": ` + nodeSpecB +
75+
`, "C": ` + nodeSpecC +
76+
`, "D": ` + nodeSpecD +
77+
` }`
78+
)
79+
80+
// TestTreeCacheNames : test tree cache operations on tree and resource names
81+
func TestTreeCacheNames(t *testing.T) {
82+
// create a tree cache
83+
treeCache := NewTreeCache()
84+
assert.NotNil(t, treeCache, "Expecting non-nil tree cache")
85+
86+
// set tree name
87+
treeCache.SetDefaultTreeName()
88+
assert.Equal(t, utils.DefaultTreeName, treeCache.GetTreeName(), "Expecting default tree name")
89+
90+
treeCache.clearTreeName()
91+
assert.Equal(t, "", treeCache.GetTreeName(), "Expecting tree name to be cleared")
92+
93+
treeName := "test-tree"
94+
treeCache.SetTreeName(treeName)
95+
assert.Equal(t, treeName, treeCache.GetTreeName(), "Expecting tree name to be set")
96+
97+
// set resource names
98+
treeCache.AddResourceName("cpu")
99+
treeCache.AddResourceNames([]string{"memory", "storage"})
100+
treeCache.DeleteResourceName("storage")
101+
finalNames := []string{"cpu", "memory"}
102+
resourceNames := treeCache.GetResourceNames()
103+
assert.ElementsMatch(t, finalNames, resourceNames, "Expecting correct resource names")
104+
numResources := treeCache.GetNumResourceNames()
105+
assert.Equal(t, len(finalNames), numResources, "Expecting matching number of resource names")
106+
107+
treeCache.clearResourceNames()
108+
resourceNames = treeCache.GetResourceNames()
109+
assert.ElementsMatch(t, []string{}, resourceNames, "Expecting empty resource names")
110+
numResources = treeCache.GetNumResourceNames()
111+
assert.Equal(t, 0, numResources, "Expecting empty resource names")
112+
113+
treeCache.SetDefaultResourceNames()
114+
resourceNames = treeCache.GetResourceNames()
115+
finalNames = utils.DefaultResourceNames
116+
assert.ElementsMatch(t, finalNames, resourceNames, "Expecting default resource names")
117+
numResources = treeCache.GetNumResourceNames()
118+
assert.Equal(t, len(finalNames), numResources, "Expecting number of default resource names")
119+
}
120+
121+
// TestTreeCacheNodes : test tree cache operations on nodes
122+
func TestTreeCacheNodes(t *testing.T) {
123+
// create a tree cache
124+
treeCache := NewTreeCache()
125+
assert.NotNil(t, treeCache, "Expecting non-nil tree cache")
126+
127+
// set tree info
128+
err := treeCache.AddTreeInfoFromString(treeInfo)
129+
assert.NoError(t, err, "Expecting no error parsing tree info string")
130+
wantTreeName := "TestTree"
131+
assert.Equal(t, wantTreeName, treeCache.GetTreeName(), "Expecting tree name to be set")
132+
resourceNames := treeCache.GetResourceNames()
133+
wantResourceNames := []string{"memory", "cpu"}
134+
assert.ElementsMatch(t, wantResourceNames, resourceNames, "Expecting correct resource names")
135+
136+
// add node specs
137+
err = treeCache.AddNodeSpecsFromString(nodesSpec)
138+
assert.NoError(t, err, "Expecting no error parsing nodes spec string")
139+
quotaTree, response := treeCache.CreateTree()
140+
testTree := createTestTree()
141+
equalTrees := reflect.DeepEqual(quotaTree, testTree)
142+
assert.True(t, equalTrees,
143+
"Expecting created tree to be same as input tree, want %v, got %v", testTree, quotaTree)
144+
assert.True(t, response.IsClean(), "Expecting clean response from tree cache")
145+
assert.ElementsMatch(t, []string{}, response.DanglingNodeNames, "Expecting no dangling nodes")
146+
147+
}
148+
149+
// createTestTree : create a test quota tree
150+
func createTestTree() *QuotaTree {
151+
// create quota nodes
152+
quotaNodeA, _ := NewQuotaNodeHard("A", &Allocation{x: []int{10, 256}}, true)
153+
quotaNodeB, _ := NewQuotaNodeHard("B", &Allocation{x: []int{2, 64}}, false)
154+
quotaNodeC, _ := NewQuotaNodeHard("C", &Allocation{x: []int{4, 128}}, false)
155+
quotaNodeD, _ := NewQuotaNodeHard("D", &Allocation{x: []int{2, 64}}, false)
156+
// connect nodes: A -> ( B C ( D ) )
157+
quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeB)))
158+
quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeC)))
159+
quotaNodeC.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeD)))
160+
// make quota tree
161+
treeName := "TestTree"
162+
resourceNames := []string{"cpu", "memory"}
163+
quotaTree := NewQuotaTree(treeName, quotaNodeA, resourceNames)
164+
return quotaTree
165+
}

0 commit comments

Comments
 (0)