Skip to content

Commit ce87762

Browse files
atantawiopenshift-merge-robot
authored andcommitted
added unit tests for TreeSnapshot and TreeController
1 parent 65a306b commit ce87762

File tree

2 files changed

+471
-0
lines changed

2 files changed

+471
-0
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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+
"strings"
22+
"testing"
23+
"unsafe"
24+
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+
// TestControllerTryUndo : test try allocate (take snapshot) and undo allocate (reinstate)
30+
func TestControllerTryUndo(t *testing.T) {
31+
treeName := "treeA"
32+
33+
// define tests
34+
var tests = []struct {
35+
name string
36+
consumer *Consumer
37+
wantAllocated bool
38+
wantErr bool
39+
}{
40+
{
41+
name: "invalid consumer wrong group",
42+
consumer: &Consumer{
43+
id: "C",
44+
treeID: treeName,
45+
groupID: "X",
46+
request: &Allocation{x: []int{1}},
47+
},
48+
wantAllocated: false,
49+
wantErr: true,
50+
},
51+
{
52+
name: "insufficient quota",
53+
consumer: &Consumer{
54+
id: "C",
55+
treeID: treeName,
56+
groupID: "C",
57+
request: &Allocation{x: []int{5}},
58+
},
59+
wantAllocated: false,
60+
wantErr: true,
61+
},
62+
{
63+
name: "consumer no preemption",
64+
consumer: &Consumer{
65+
id: "C",
66+
treeID: treeName,
67+
groupID: "C",
68+
request: &Allocation{x: []int{1}},
69+
},
70+
wantAllocated: true,
71+
wantErr: false,
72+
},
73+
{
74+
name: "consumer preemption",
75+
consumer: &Consumer{
76+
id: "C",
77+
treeID: treeName,
78+
groupID: "B",
79+
request: &Allocation{x: []int{1}},
80+
},
81+
wantAllocated: true,
82+
wantErr: false,
83+
},
84+
{
85+
name: "high priority consumer no preemption",
86+
consumer: &Consumer{
87+
id: "C",
88+
treeID: treeName,
89+
groupID: "C",
90+
request: &Allocation{x: []int{1}},
91+
priority: 1,
92+
},
93+
wantAllocated: true,
94+
wantErr: false,
95+
},
96+
{
97+
name: "high priority consumer preemption",
98+
consumer: &Consumer{
99+
id: "C",
100+
treeID: treeName,
101+
groupID: "B",
102+
request: &Allocation{x: []int{2}},
103+
priority: 1,
104+
},
105+
wantAllocated: true,
106+
wantErr: false,
107+
},
108+
}
109+
110+
// Before keeps the state of the quota tree before applying test cases
111+
ctlrBefore := createTestContoller(t, treeName)
112+
113+
// perform tests
114+
for _, tt := range tests {
115+
t.Run(tt.name, func(t *testing.T) {
116+
// After keeps the state of the quota tree after applying a test case
117+
ctlrAfter := createTestContoller(t, tt.consumer.treeID)
118+
119+
response := ctlrAfter.TryAllocate(tt.consumer)
120+
if err := response == nil || len(strings.TrimSpace(response.GetMessage())) > 0; err != tt.wantErr {
121+
t.Errorf("TryAllocate() response error, want %v", tt.wantErr)
122+
}
123+
if treeChanged := !EqualStateControllers(ctlrBefore, ctlrAfter); treeChanged != tt.wantAllocated {
124+
t.Errorf("TryAllocate() tree changed = %v, want %v", treeChanged, tt.wantAllocated)
125+
}
126+
if undone := ctlrAfter.UndoAllocate(tt.consumer); !undone {
127+
t.Errorf("TryAllocate() undo failed, want %v", tt.wantErr)
128+
}
129+
if !EqualStateControllers(ctlrAfter, ctlrBefore) {
130+
t.Errorf("UndoAllocate() = %v, want %v", ctlrAfter, ctlrBefore)
131+
}
132+
})
133+
}
134+
}
135+
136+
// createTestContoller : create an initial test quota tree with allocated consumers
137+
func createTestContoller(t *testing.T, treeName string) *Controller {
138+
139+
// create three quota nodes A[3], B[1], and C[1]
140+
quotaNodeA, err := NewQuotaNode("A", &Allocation{x: []int{3}})
141+
assert.NoError(t, err, "No error expected when creating quota node A")
142+
143+
quotaNodeB, err := NewQuotaNode("B", &Allocation{x: []int{1}})
144+
assert.NoError(t, err, "No error expected when creating quota node B")
145+
146+
quotaNodeC, err := NewQuotaNode("C", &Allocation{x: []int{1}})
147+
assert.NoError(t, err, "No error expected when creating quota node C")
148+
149+
// create two consumers C1[1] and C2[1]
150+
consumer1 := NewConsumer("C1", treeName, "B", &Allocation{x: []int{1}}, 0, 0, false)
151+
consumer2 := NewConsumer("C2", treeName, "B", &Allocation{x: []int{1}}, 0, 0, false)
152+
153+
// create quota tree: A -> ( B C )
154+
quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeB)))
155+
quotaNodeA.AddChild((*tree.Node)(unsafe.Pointer(quotaNodeC)))
156+
quotaTreeA := NewQuotaTree(treeName, quotaNodeA, []string{"count"})
157+
controller := NewController(quotaTreeA)
158+
159+
// allocate consumers C1 and C2
160+
response1 := controller.Allocate(consumer1)
161+
assert.NotNil(t, response1, "A non nill response 1 is expected")
162+
assert.Equal(t, 0, len(strings.TrimSpace(response1.GetMessage())), "A empty response 1 is expected")
163+
164+
response2 := controller.Allocate(consumer2)
165+
assert.NotNil(t, response2, "A non nill response 2 is expected")
166+
assert.Equal(t, 0, len(strings.TrimSpace(response2.GetMessage())), "A empty response 2 is expected")
167+
168+
return controller
169+
}
170+
171+
// EqualStateQuotaNodes : check if two quota nodes have similar allocation data
172+
func EqualStateQuotaNodes(qn1 *QuotaNode, qn2 *QuotaNode) bool {
173+
return reflect.DeepEqual(qn1.GetQuota(), qn2.GetQuota()) &&
174+
reflect.DeepEqual(qn1.GetAllocated(), qn2.GetAllocated()) &&
175+
reflect.DeepEqual(qn1.GetConsumers(), qn2.GetConsumers())
176+
}
177+
178+
// EqualStateQuotaTrees : check if two quota trees have similar allocation data
179+
func EqualStateQuotaTrees(qt1 *QuotaTree, qt2 *QuotaTree) bool {
180+
nodeMap1 := qt1.GetNodes()
181+
nodeMap2 := qt2.GetNodes()
182+
if len(nodeMap1) != len(nodeMap2) {
183+
return false
184+
}
185+
for k, qn1 := range nodeMap1 {
186+
if qn2, exists := nodeMap2[k]; exists {
187+
if !EqualStateQuotaNodes(qn1, qn2) {
188+
return false
189+
}
190+
} else {
191+
return false
192+
}
193+
}
194+
return true
195+
}
196+
197+
// EqualStateControllers : check if two controllers have similar allocation state
198+
func EqualStateControllers(c1 *Controller, c2 *Controller) bool {
199+
return EqualStateQuotaTrees(c1.tree, c2.tree) &&
200+
reflect.DeepEqual(c1.consumers, c1.consumers) &&
201+
reflect.DeepEqual(c1.preemptedConsumers, c2.preemptedConsumers) &&
202+
reflect.DeepEqual(c1.preemptedConsumersArray, c2.preemptedConsumersArray)
203+
}

0 commit comments

Comments
 (0)