Skip to content

Commit 40dbe27

Browse files
authored
Merge pull request #22 from arangodb/plan_builder_tests
UnitTests for plan_builder
2 parents efc9fa1 + abf9275 commit 40dbe27

File tree

3 files changed

+261
-19
lines changed

3 files changed

+261
-19
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ run-unit-tests: $(GOBUILDDIR) $(SOURCES)
167167
golang:$(GOVERSION) \
168168
go test -v \
169169
$(REPOPATH)/pkg/apis/arangodb/v1alpha \
170+
$(REPOPATH)/pkg/deployment \
170171
$(REPOPATH)/pkg/util/k8sutil
171172

172173
$(TESTBIN): $(GOBUILDDIR) $(SOURCES)

pkg/deployment/plan_builder.go

+33-19
Original file line numberDiff line numberDiff line change
@@ -31,41 +31,55 @@ import (
3131
// get the status in line with the specification.
3232
// If a plan already exists, nothing is done.
3333
func (d *Deployment) createPlan() error {
34-
if len(d.status.Plan) > 0 {
35-
// Plan already exists, complete that first
34+
// Create plan
35+
newPlan, changed := createPlan(d.deps.Log, d.status.Plan, d.apiObject.Spec, d.status)
36+
37+
// If not change, we're done
38+
if !changed {
39+
return nil
40+
}
41+
42+
// Save plan
43+
if len(newPlan) == 0 {
44+
// Nothing to do
3645
return nil
3746
}
47+
d.status.Plan = newPlan
48+
if err := d.updateCRStatus(); err != nil {
49+
return maskAny(err)
50+
}
51+
return nil
52+
}
53+
54+
// createPlan considers the given specification & status and creates a plan to get the status in line with the specification.
55+
// If a plan already exists, the given plan is returned with false.
56+
// Otherwise the new plan is returned with a boolean true.
57+
func createPlan(log zerolog.Logger, currentPlan api.Plan, spec api.DeploymentSpec, status api.DeploymentStatus) (api.Plan, bool) {
58+
if len(currentPlan) > 0 {
59+
// Plan already exists, complete that first
60+
return currentPlan, false
61+
}
3862

3963
// Check for various scenario's
40-
spec := d.apiObject.Spec
4164
var plan api.Plan
42-
log := d.deps.Log
4365

4466
// Check for scale up/down
4567
switch spec.Mode {
4668
case api.DeploymentModeSingle:
4769
// Never scale down
4870
case api.DeploymentModeResilientSingle:
4971
// Only scale singles
50-
plan = append(plan, createScalePlan(log, d.status.Members.Single, api.ServerGroupSingle, spec.Single.Count)...)
72+
plan = append(plan, createScalePlan(log, status.Members.Single, api.ServerGroupSingle, spec.Single.Count)...)
5173
case api.DeploymentModeCluster:
5274
// Scale dbservers, coordinators, syncmasters & syncworkers
53-
plan = append(plan, createScalePlan(log, d.status.Members.DBServers, api.ServerGroupDBServers, spec.DBServers.Count)...)
54-
plan = append(plan, createScalePlan(log, d.status.Members.Coordinators, api.ServerGroupCoordinators, spec.Coordinators.Count)...)
55-
plan = append(plan, createScalePlan(log, d.status.Members.SyncMasters, api.ServerGroupSyncMasters, spec.SyncMasters.Count)...)
56-
plan = append(plan, createScalePlan(log, d.status.Members.SyncWorkers, api.ServerGroupSyncWorkers, spec.SyncWorkers.Count)...)
75+
plan = append(plan, createScalePlan(log, status.Members.DBServers, api.ServerGroupDBServers, spec.DBServers.Count)...)
76+
plan = append(plan, createScalePlan(log, status.Members.Coordinators, api.ServerGroupCoordinators, spec.Coordinators.Count)...)
77+
plan = append(plan, createScalePlan(log, status.Members.SyncMasters, api.ServerGroupSyncMasters, spec.SyncMasters.Count)...)
78+
plan = append(plan, createScalePlan(log, status.Members.SyncWorkers, api.ServerGroupSyncWorkers, spec.SyncWorkers.Count)...)
5779
}
5880

59-
// Save plan
60-
if len(plan) == 0 {
61-
// Nothing to do
62-
return nil
63-
}
64-
d.status.Plan = plan
65-
if err := d.updateCRStatus(); err != nil {
66-
return maskAny(err)
67-
}
68-
return nil
81+
// Return plan
82+
return plan, true
6983
}
7084

7185
// createScalePlan creates a scaling plan for a single server group

pkg/deployment/plan_builder_test.go

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
// Author Ewout Prangsma
21+
//
22+
23+
package deployment
24+
25+
import (
26+
"testing"
27+
28+
"github.com/rs/zerolog"
29+
"github.com/stretchr/testify/assert"
30+
"github.com/stretchr/testify/require"
31+
32+
api "github.com/arangodb/k8s-operator/pkg/apis/arangodb/v1alpha"
33+
)
34+
35+
// TestCreatePlanSingleScale creates a `single` deployment to test the creating of scaling plan.
36+
func TestCreatePlanSingleScale(t *testing.T) {
37+
log := zerolog.Nop()
38+
spec := api.DeploymentSpec{
39+
Mode: api.DeploymentModeSingle,
40+
}
41+
spec.SetDefaults("test")
42+
43+
// Test with empty status
44+
var status api.DeploymentStatus
45+
newPlan, changed := createPlan(log, nil, spec, status)
46+
assert.True(t, changed)
47+
assert.Len(t, newPlan, 0) // Single mode does not scale
48+
49+
// Test with 1 single member
50+
status.Members.Single = api.MemberStatusList{
51+
api.MemberStatus{
52+
ID: "id",
53+
PodName: "something",
54+
},
55+
}
56+
newPlan, changed = createPlan(log, nil, spec, status)
57+
assert.True(t, changed)
58+
assert.Len(t, newPlan, 0) // Single mode does not scale
59+
60+
// Test with 2 single members (which should not happen) and try to scale down
61+
status.Members.Single = api.MemberStatusList{
62+
api.MemberStatus{
63+
ID: "id1",
64+
PodName: "something1",
65+
},
66+
api.MemberStatus{
67+
ID: "id1",
68+
PodName: "something1",
69+
},
70+
}
71+
newPlan, changed = createPlan(log, nil, spec, status)
72+
assert.True(t, changed)
73+
assert.Len(t, newPlan, 0) // Single mode does not scale
74+
}
75+
76+
// TestCreatePlanResilientSingleScale creates a `resilientsingle` deployment to test the creating of scaling plan.
77+
func TestCreatePlanResilientSingleScale(t *testing.T) {
78+
log := zerolog.Nop()
79+
spec := api.DeploymentSpec{
80+
Mode: api.DeploymentModeResilientSingle,
81+
}
82+
spec.SetDefaults("test")
83+
spec.Single.Count = 2
84+
85+
// Test with empty status
86+
var status api.DeploymentStatus
87+
newPlan, changed := createPlan(log, nil, spec, status)
88+
assert.True(t, changed)
89+
require.Len(t, newPlan, 2)
90+
assert.Equal(t, api.ActionTypeAddMember, newPlan[0].Type)
91+
assert.Equal(t, api.ActionTypeAddMember, newPlan[1].Type)
92+
93+
// Test with 1 single member
94+
status.Members.Single = api.MemberStatusList{
95+
api.MemberStatus{
96+
ID: "id",
97+
PodName: "something",
98+
},
99+
}
100+
newPlan, changed = createPlan(log, nil, spec, status)
101+
assert.True(t, changed)
102+
require.Len(t, newPlan, 1)
103+
assert.Equal(t, api.ActionTypeAddMember, newPlan[0].Type)
104+
assert.Equal(t, api.ServerGroupSingle, newPlan[0].Group)
105+
106+
// Test scaling down from 4 members to 2
107+
status.Members.Single = api.MemberStatusList{
108+
api.MemberStatus{
109+
ID: "id1",
110+
PodName: "something1",
111+
},
112+
api.MemberStatus{
113+
ID: "id2",
114+
PodName: "something2",
115+
},
116+
api.MemberStatus{
117+
ID: "id3",
118+
PodName: "something3",
119+
},
120+
api.MemberStatus{
121+
ID: "id4",
122+
PodName: "something4",
123+
},
124+
}
125+
newPlan, changed = createPlan(log, nil, spec, status)
126+
assert.True(t, changed)
127+
require.Len(t, newPlan, 2) // Note: Downscaling is only down 1 at a time
128+
assert.Equal(t, api.ActionTypeShutdownMember, newPlan[0].Type)
129+
assert.Equal(t, api.ActionTypeRemoveMember, newPlan[1].Type)
130+
assert.Equal(t, api.ServerGroupSingle, newPlan[0].Group)
131+
assert.Equal(t, api.ServerGroupSingle, newPlan[1].Group)
132+
}
133+
134+
// TestCreatePlanClusterScale creates a `cluster` deployment to test the creating of scaling plan.
135+
func TestCreatePlanClusterScale(t *testing.T) {
136+
log := zerolog.Nop()
137+
spec := api.DeploymentSpec{
138+
Mode: api.DeploymentModeCluster,
139+
}
140+
spec.SetDefaults("test")
141+
142+
// Test with empty status
143+
var status api.DeploymentStatus
144+
newPlan, changed := createPlan(log, nil, spec, status)
145+
assert.True(t, changed)
146+
require.Len(t, newPlan, 6) // Adding 3 dbservers & 3 coordinators (note: agents do not scale now)
147+
assert.Equal(t, api.ActionTypeAddMember, newPlan[0].Type)
148+
assert.Equal(t, api.ActionTypeAddMember, newPlan[1].Type)
149+
assert.Equal(t, api.ActionTypeAddMember, newPlan[2].Type)
150+
assert.Equal(t, api.ActionTypeAddMember, newPlan[3].Type)
151+
assert.Equal(t, api.ActionTypeAddMember, newPlan[4].Type)
152+
assert.Equal(t, api.ActionTypeAddMember, newPlan[5].Type)
153+
assert.Equal(t, api.ServerGroupDBServers, newPlan[0].Group)
154+
assert.Equal(t, api.ServerGroupDBServers, newPlan[1].Group)
155+
assert.Equal(t, api.ServerGroupDBServers, newPlan[2].Group)
156+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[3].Group)
157+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[4].Group)
158+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[5].Group)
159+
160+
// Test with 2 dbservers & 1 coordinator
161+
status.Members.DBServers = api.MemberStatusList{
162+
api.MemberStatus{
163+
ID: "db1",
164+
PodName: "something1",
165+
},
166+
api.MemberStatus{
167+
ID: "db2",
168+
PodName: "something2",
169+
},
170+
}
171+
status.Members.Coordinators = api.MemberStatusList{
172+
api.MemberStatus{
173+
ID: "cr1",
174+
PodName: "coordinator1",
175+
},
176+
}
177+
newPlan, changed = createPlan(log, nil, spec, status)
178+
assert.True(t, changed)
179+
require.Len(t, newPlan, 3)
180+
assert.Equal(t, api.ActionTypeAddMember, newPlan[0].Type)
181+
assert.Equal(t, api.ActionTypeAddMember, newPlan[1].Type)
182+
assert.Equal(t, api.ActionTypeAddMember, newPlan[2].Type)
183+
assert.Equal(t, api.ServerGroupDBServers, newPlan[0].Group)
184+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[1].Group)
185+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[2].Group)
186+
187+
// Now scale down
188+
status.Members.DBServers = api.MemberStatusList{
189+
api.MemberStatus{
190+
ID: "db1",
191+
PodName: "something1",
192+
},
193+
api.MemberStatus{
194+
ID: "db2",
195+
PodName: "something2",
196+
},
197+
api.MemberStatus{
198+
ID: "db3",
199+
PodName: "something3",
200+
},
201+
}
202+
status.Members.Coordinators = api.MemberStatusList{
203+
api.MemberStatus{
204+
ID: "cr1",
205+
PodName: "coordinator1",
206+
},
207+
api.MemberStatus{
208+
ID: "cr2",
209+
PodName: "coordinator2",
210+
},
211+
}
212+
spec.DBServers.Count = 1
213+
spec.Coordinators.Count = 1
214+
newPlan, changed = createPlan(log, nil, spec, status)
215+
assert.True(t, changed)
216+
require.Len(t, newPlan, 5) // Note: Downscaling is done 1 at a time
217+
assert.Equal(t, api.ActionTypeCleanOutMember, newPlan[0].Type)
218+
assert.Equal(t, api.ActionTypeShutdownMember, newPlan[1].Type)
219+
assert.Equal(t, api.ActionTypeRemoveMember, newPlan[2].Type)
220+
assert.Equal(t, api.ActionTypeShutdownMember, newPlan[3].Type)
221+
assert.Equal(t, api.ActionTypeRemoveMember, newPlan[4].Type)
222+
assert.Equal(t, api.ServerGroupDBServers, newPlan[0].Group)
223+
assert.Equal(t, api.ServerGroupDBServers, newPlan[1].Group)
224+
assert.Equal(t, api.ServerGroupDBServers, newPlan[2].Group)
225+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[3].Group)
226+
assert.Equal(t, api.ServerGroupCoordinators, newPlan[4].Group)
227+
}

0 commit comments

Comments
 (0)