Skip to content
This repository was archived by the owner on Jan 21, 2020. It is now read-only.

Commit 61b254a

Browse files
author
David Chung
authored
Add tests for the Manager plugin (#306)
Signed-off-by: David Chung <[email protected]>
1 parent 06ab6e7 commit 61b254a

File tree

7 files changed

+450
-7
lines changed

7 files changed

+450
-7
lines changed

pkg/manager/manager_test.go

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package manager
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/docker/infrakit/pkg/discovery"
12+
"github.com/docker/infrakit/pkg/leader"
13+
group_mock "github.com/docker/infrakit/pkg/mock/spi/group"
14+
store_mock "github.com/docker/infrakit/pkg/mock/store"
15+
group_rpc "github.com/docker/infrakit/pkg/rpc/group"
16+
"github.com/docker/infrakit/pkg/rpc/server"
17+
"github.com/docker/infrakit/pkg/spi/group"
18+
"github.com/golang/mock/gomock"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
type testLeaderDetector struct {
23+
t *testing.T
24+
me string
25+
input <-chan string
26+
stop chan struct{}
27+
ch chan leader.Leadership
28+
}
29+
30+
func (l *testLeaderDetector) Start() (<-chan leader.Leadership, error) {
31+
l.stop = make(chan struct{})
32+
l.ch = make(chan leader.Leadership)
33+
go func() {
34+
for {
35+
select {
36+
case <-l.stop:
37+
return
38+
case found := <-l.input:
39+
if found == l.me {
40+
l.ch <- leader.Leadership{Status: leader.Leader}
41+
} else {
42+
l.ch <- leader.Leadership{Status: leader.NotLeader}
43+
}
44+
}
45+
}
46+
}()
47+
return l.ch, nil
48+
}
49+
50+
func (l *testLeaderDetector) Stop() {
51+
close(l.stop)
52+
}
53+
54+
func testEnsemble(t *testing.T,
55+
dir, id string,
56+
leader chan string,
57+
ctrl *gomock.Controller,
58+
configStore func(*store_mock.MockSnapshot),
59+
configureGroup func(*group_mock.MockPlugin)) (Backend, server.Stoppable) {
60+
61+
disc, err := discovery.NewPluginDiscoveryWithDirectory(dir)
62+
require.NoError(t, err)
63+
64+
detector := &testLeaderDetector{t: t, me: id, input: leader}
65+
66+
snap := store_mock.NewMockSnapshot(ctrl)
67+
configStore(snap)
68+
69+
// start group
70+
gm := group_mock.NewMockPlugin(ctrl)
71+
configureGroup(gm)
72+
73+
gs := group_rpc.PluginServer(gm)
74+
st, err := server.StartPluginAtPath(filepath.Join(dir, "group-stateless"), gs)
75+
require.NoError(t, err)
76+
77+
m, err := NewManager(disc, detector, snap, "group-stateless")
78+
require.NoError(t, err)
79+
80+
return m, st
81+
}
82+
83+
func testSetLeader(t *testing.T, c []chan string, l string) {
84+
for _, cc := range c {
85+
cc <- l
86+
}
87+
}
88+
89+
func testDiscoveryDir(t *testing.T) string {
90+
dir := filepath.Join(os.TempDir(), fmt.Sprintf("%v", time.Now().UnixNano()))
91+
err := os.MkdirAll(dir, 0744)
92+
require.NoError(t, err)
93+
return dir
94+
}
95+
96+
func testBuildGroupSpec(groupID, properties string) group.Spec {
97+
raw := json.RawMessage([]byte(properties))
98+
return group.Spec{
99+
ID: group.ID(groupID),
100+
Properties: &raw,
101+
}
102+
}
103+
104+
func testBuildGlobalSpec(t *testing.T, gs group.Spec) GlobalSpec {
105+
buff, err := json.Marshal(gs)
106+
require.NoError(t, err)
107+
raw := json.RawMessage(buff)
108+
return GlobalSpec{
109+
Groups: map[group.ID]PluginSpec{
110+
gs.ID: {
111+
Plugin: "group-stateless",
112+
Properties: &raw,
113+
},
114+
},
115+
}
116+
}
117+
118+
func testToStruct(m *json.RawMessage) interface{} {
119+
o := map[string]interface{}{}
120+
json.Unmarshal([]byte(*m), &o)
121+
return &o
122+
}
123+
124+
func TestNoCallsToGroupWhenNoLeader(t *testing.T) {
125+
ctrl := gomock.NewController(t)
126+
defer ctrl.Finish()
127+
128+
leaderChans := []chan string{make(chan string), make(chan string)}
129+
130+
manager1, stoppable1 := testEnsemble(t, testDiscoveryDir(t), "m1", leaderChans[0], ctrl,
131+
func(s *store_mock.MockSnapshot) {
132+
// no calls
133+
},
134+
func(g *group_mock.MockPlugin) {
135+
// no calls
136+
})
137+
manager2, stoppable2 := testEnsemble(t, testDiscoveryDir(t), "m2", leaderChans[1], ctrl,
138+
func(s *store_mock.MockSnapshot) {
139+
// no calls
140+
},
141+
func(g *group_mock.MockPlugin) {
142+
// no calls
143+
})
144+
145+
manager1.Start()
146+
manager2.Start()
147+
148+
manager1.Stop()
149+
manager2.Stop()
150+
151+
stoppable1.Stop()
152+
stoppable2.Stop()
153+
}
154+
155+
func TestStartOneLeader(t *testing.T) {
156+
ctrl := gomock.NewController(t)
157+
defer ctrl.Finish()
158+
159+
gs := testBuildGroupSpec("managers", `
160+
{
161+
"field1": "value1"
162+
}
163+
`)
164+
global := testBuildGlobalSpec(t, gs)
165+
166+
leaderChans := []chan string{make(chan string), make(chan string)}
167+
checkpoint := make(chan struct{})
168+
169+
manager1, stoppable1 := testEnsemble(t, testDiscoveryDir(t), "m1", leaderChans[0], ctrl,
170+
func(s *store_mock.MockSnapshot) {
171+
empty := &GlobalSpec{}
172+
s.EXPECT().Load(gomock.Eq(empty)).Do(
173+
func(o interface{}) error {
174+
p, is := o.(*GlobalSpec)
175+
require.True(t, is)
176+
*p = global
177+
return nil
178+
}).Return(nil)
179+
},
180+
func(g *group_mock.MockPlugin) {
181+
g.EXPECT().CommitGroup(gomock.Any(), false).Do(
182+
func(spec group.Spec, pretend bool) (string, error) {
183+
184+
defer close(checkpoint)
185+
186+
require.Equal(t, gs.ID, spec.ID)
187+
require.Equal(t, testToStruct(gs.Properties), testToStruct(spec.Properties))
188+
return "ok", nil
189+
}).Return("ok", nil)
190+
})
191+
manager2, stoppable2 := testEnsemble(t, testDiscoveryDir(t), "m2", leaderChans[1], ctrl,
192+
func(s *store_mock.MockSnapshot) {
193+
// no calls expected
194+
},
195+
func(g *group_mock.MockPlugin) {
196+
// no calls expected
197+
})
198+
199+
manager1.Start()
200+
manager2.Start()
201+
202+
testSetLeader(t, leaderChans, "m1")
203+
204+
<-checkpoint
205+
206+
manager1.Stop()
207+
manager2.Stop()
208+
209+
stoppable1.Stop()
210+
stoppable2.Stop()
211+
212+
}
213+
214+
func TestChangeLeadership(t *testing.T) {
215+
ctrl := gomock.NewController(t)
216+
defer ctrl.Finish()
217+
218+
gs := testBuildGroupSpec("managers", `
219+
{
220+
"field1": "value1"
221+
}
222+
`)
223+
global := testBuildGlobalSpec(t, gs)
224+
225+
leaderChans := []chan string{make(chan string), make(chan string)}
226+
checkpoint1 := make(chan struct{})
227+
checkpoint2 := make(chan struct{})
228+
checkpoint3 := make(chan struct{})
229+
230+
manager1, stoppable1 := testEnsemble(t, testDiscoveryDir(t), "m1", leaderChans[0], ctrl,
231+
func(s *store_mock.MockSnapshot) {
232+
empty := &GlobalSpec{}
233+
s.EXPECT().Load(gomock.Eq(empty)).Do(
234+
func(o interface{}) error {
235+
p, is := o.(*GlobalSpec)
236+
require.True(t, is)
237+
*p = global
238+
return nil
239+
},
240+
).Return(nil)
241+
},
242+
func(g *group_mock.MockPlugin) {
243+
g.EXPECT().CommitGroup(gomock.Any(), false).Do(
244+
func(spec group.Spec, pretend bool) (string, error) {
245+
246+
defer close(checkpoint1)
247+
248+
require.Equal(t, gs.ID, spec.ID)
249+
require.Equal(t, testToStruct(gs.Properties), testToStruct(spec.Properties))
250+
return "ok", nil
251+
},
252+
).Return("ok", nil)
253+
254+
// We will get a call to inspect what's being watched
255+
g.EXPECT().InspectGroups().Return([]group.Spec{gs}, nil)
256+
257+
// Now we lost leadership... need to unwatch
258+
g.EXPECT().FreeGroup(gomock.Eq(group.ID("managers"))).Do(
259+
func(id group.ID) error {
260+
261+
defer close(checkpoint3)
262+
263+
return nil
264+
},
265+
).Return(nil)
266+
})
267+
manager2, stoppable2 := testEnsemble(t, testDiscoveryDir(t), "m2", leaderChans[1], ctrl,
268+
func(s *store_mock.MockSnapshot) {
269+
empty := &GlobalSpec{}
270+
s.EXPECT().Load(gomock.Eq(empty)).Do(
271+
func(o interface{}) error {
272+
p, is := o.(*GlobalSpec)
273+
require.True(t, is)
274+
*p = global
275+
return nil
276+
},
277+
).Return(nil)
278+
},
279+
func(g *group_mock.MockPlugin) {
280+
g.EXPECT().CommitGroup(gomock.Any(), false).Do(
281+
func(spec group.Spec, pretend bool) (string, error) {
282+
283+
defer close(checkpoint2)
284+
285+
require.Equal(t, gs.ID, spec.ID)
286+
require.Equal(t, testToStruct(gs.Properties), testToStruct(spec.Properties))
287+
return "ok", nil
288+
},
289+
).Return("ok", nil)
290+
})
291+
292+
manager1.Start()
293+
manager2.Start()
294+
295+
testSetLeader(t, leaderChans, "m1")
296+
297+
<-checkpoint1
298+
299+
testSetLeader(t, leaderChans, "m2")
300+
301+
<-checkpoint2
302+
<-checkpoint3
303+
304+
manager1.Stop()
305+
manager2.Stop()
306+
307+
stoppable1.Stop()
308+
stoppable2.Stop()
309+
}

pkg/mock/generate.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package mock
22

3-
//go:generate mockgen -package instance -destination spi/instance/instance.go github.com/docker/infrakit/spi/instance Plugin
4-
//go:generate mockgen -package instance -destination spi/flavor/flavor.go github.com/docker/infrakit/spi/flavor Plugin
3+
//go:generate mockgen -package instance -destination spi/instance/instance.go github.com/docker/infrakit/pkg/spi/instance Plugin
4+
//go:generate mockgen -package flavor -destination spi/flavor/flavor.go github.com/docker/infrakit/pkg/spi/flavor Plugin
5+
//go:generate mockgen -package group -destination spi/group/group.go github.com/docker/infrakit/pkg/spi/group Plugin
56
//go:generate mockgen -package client -destination docker/docker/client/api.go github.com/docker/docker/client APIClient
6-
//go:generate mockgen -package group -destination plugin/group/group.go github.com/docker/infrakit/plugin/group Scaled
7+
//go:generate mockgen -package group -destination plugin/group/group.go github.com/docker/infrakit/pkg/plugin/group Scaled
8+
//go:generate mockgen -package store -destination store/store.go github.com/docker/infrakit/pkg/store Snapshot

pkg/mock/plugin/group/group.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Automatically generated by MockGen. DO NOT EDIT!
2-
// Source: github.com/docker/infrakit/plugin/group (interfaces: Scaled)
2+
// Source: github.com/docker/infrakit/pkg/plugin/group (interfaces: Scaled)
33

44
package group
55

pkg/mock/spi/flavor/flavor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Automatically generated by MockGen. DO NOT EDIT!
2-
// Source: github.com/docker/infrakit/spi/flavor (interfaces: Plugin)
2+
// Source: github.com/docker/infrakit/pkg/spi/flavor (interfaces: Plugin)
33

4-
package instance
4+
package flavor
55

66
import (
77
json "encoding/json"

0 commit comments

Comments
 (0)