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

Commit 3f2e6fb

Browse files
dgageotDavid Chung
authored andcommitted
Implement bootstrap nodes relabelling (#403)
Fixes #402 Signed-off-by: David Gageot <[email protected]>
1 parent 7841ecd commit 3f2e6fb

File tree

5 files changed

+340
-6
lines changed

5 files changed

+340
-6
lines changed

pkg/mock/plugin/group/group.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ func (_mr *_MockScaledRecorder) Health(arg0 interface{}) *gomock.Call {
5656
return _mr.mock.ctrl.RecordCall(_mr.mock, "Health", arg0)
5757
}
5858

59+
func (_m *MockScaled) Label() error {
60+
ret := _m.ctrl.Call(_m, "Label")
61+
ret0, _ := ret[0].(error)
62+
return ret0
63+
}
64+
65+
func (_mr *_MockScaledRecorder) Label() *gomock.Call {
66+
return _mr.mock.ctrl.RecordCall(_mr.mock, "Label")
67+
}
68+
5969
func (_m *MockScaled) List() ([]instance.Description, error) {
6070
ret := _m.ctrl.Call(_m, "List")
6171
ret0, _ := ret[0].([]instance.Description)

pkg/plugin/group/scaled.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ package group
22

33
import (
44
"fmt"
5+
"sync"
56

67
log "github.com/Sirupsen/logrus"
78
"github.com/docker/infrakit/pkg/spi/flavor"
89
"github.com/docker/infrakit/pkg/spi/instance"
910
"github.com/docker/infrakit/pkg/types"
10-
"sync"
11+
)
12+
13+
const (
14+
bootstrapConfigTag = "bootstrap"
1115
)
1216

1317
// Scaled is a collection of instances that can be scaled up and down.
@@ -24,6 +28,9 @@ type Scaled interface {
2428

2529
// List returns all instances in the group.
2630
List() ([]instance.Description, error)
31+
32+
// Label makes sure all instances in the group are labelled.
33+
Label() error
2734
}
2835

2936
type scaledGroup struct {
@@ -118,3 +125,44 @@ func (s *scaledGroup) List() ([]instance.Description, error) {
118125

119126
return settings.instancePlugin.DescribeInstances(s.memberTags)
120127
}
128+
129+
func (s *scaledGroup) Label() error {
130+
settings := s.latestSettings()
131+
132+
instances, err := settings.instancePlugin.DescribeInstances(s.memberTags)
133+
if err != nil {
134+
return err
135+
}
136+
137+
tagsWithConfigSha := map[string]string{}
138+
for k, v := range s.memberTags {
139+
tagsWithConfigSha[k] = v
140+
}
141+
tagsWithConfigSha[configTag] = settings.config.InstanceHash()
142+
143+
for _, inst := range instances {
144+
if instanceNeedsLabel(inst) {
145+
log.Infof("Labelling instance %s", inst.ID)
146+
147+
if err := settings.instancePlugin.Label(inst.ID, tagsWithConfigSha); err != nil {
148+
return err
149+
}
150+
}
151+
}
152+
153+
return nil
154+
}
155+
156+
func needsLabel(instances []instance.Description) bool {
157+
for _, inst := range instances {
158+
if instanceNeedsLabel(inst) {
159+
return true
160+
}
161+
}
162+
163+
return false
164+
}
165+
166+
func instanceNeedsLabel(instance instance.Description) bool {
167+
return instance.Tags[configTag] == bootstrapConfigTag
168+
}

pkg/plugin/group/scaled_test.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package group
2+
3+
import (
4+
"testing"
5+
6+
mock_instance "github.com/docker/infrakit/pkg/mock/spi/instance"
7+
"github.com/docker/infrakit/pkg/plugin/group/types"
8+
"github.com/docker/infrakit/pkg/spi/instance"
9+
"github.com/golang/mock/gomock"
10+
"github.com/pkg/errors"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestLabelEmpty(t *testing.T) {
15+
ctrl := gomock.NewController(t)
16+
defer ctrl.Finish()
17+
18+
tags := map[string]string{
19+
"key": "value",
20+
}
21+
22+
instancePlugin := mock_instance.NewMockPlugin(ctrl)
23+
scaled := &scaledGroup{
24+
settings: groupSettings{
25+
instancePlugin: instancePlugin,
26+
},
27+
memberTags: tags,
28+
}
29+
30+
gomock.InOrder(
31+
instancePlugin.EXPECT().DescribeInstances(tags).Return([]instance.Description{}, nil),
32+
)
33+
34+
err := scaled.Label()
35+
36+
require.NoError(t, err)
37+
}
38+
39+
func TestLabelError(t *testing.T) {
40+
ctrl := gomock.NewController(t)
41+
defer ctrl.Finish()
42+
43+
tags := map[string]string{
44+
"key": "value",
45+
}
46+
47+
instancePlugin := mock_instance.NewMockPlugin(ctrl)
48+
scaled := &scaledGroup{
49+
settings: groupSettings{
50+
instancePlugin: instancePlugin,
51+
},
52+
memberTags: tags,
53+
}
54+
55+
gomock.InOrder(
56+
instancePlugin.EXPECT().DescribeInstances(tags).Return(nil, errors.New("BUG")),
57+
)
58+
59+
err := scaled.Label()
60+
61+
require.Error(t, err)
62+
}
63+
64+
func TestLabelAllLabelled(t *testing.T) {
65+
ctrl := gomock.NewController(t)
66+
defer ctrl.Finish()
67+
68+
tags := map[string]string{
69+
"key": "value",
70+
}
71+
72+
instancePlugin := mock_instance.NewMockPlugin(ctrl)
73+
scaled := &scaledGroup{
74+
settings: groupSettings{
75+
instancePlugin: instancePlugin,
76+
},
77+
memberTags: tags,
78+
}
79+
80+
gomock.InOrder(
81+
instancePlugin.EXPECT().DescribeInstances(tags).Return([]instance.Description{
82+
{
83+
ID: instance.ID("labbeled1"),
84+
Tags: map[string]string{
85+
"key": "value",
86+
configTag: "SHA",
87+
},
88+
},
89+
{
90+
ID: instance.ID("labbeled2"),
91+
Tags: map[string]string{
92+
"key": "value",
93+
configTag: "SHA",
94+
},
95+
},
96+
}, nil),
97+
)
98+
99+
err := scaled.Label()
100+
101+
require.NoError(t, err)
102+
}
103+
104+
func TestLabelOneUnlabelled(t *testing.T) {
105+
ctrl := gomock.NewController(t)
106+
defer ctrl.Finish()
107+
108+
config := types.Spec{}
109+
tags := map[string]string{
110+
"key": "value",
111+
}
112+
113+
instancePlugin := mock_instance.NewMockPlugin(ctrl)
114+
scaled := &scaledGroup{
115+
settings: groupSettings{
116+
instancePlugin: instancePlugin,
117+
config: config,
118+
},
119+
memberTags: tags,
120+
}
121+
122+
gomock.InOrder(
123+
instancePlugin.EXPECT().DescribeInstances(tags).Return([]instance.Description{
124+
{
125+
ID: instance.ID("labbeled"),
126+
Tags: map[string]string{
127+
"key": "value",
128+
configTag: config.InstanceHash(),
129+
},
130+
},
131+
{
132+
ID: instance.ID("unlabelled"),
133+
Tags: map[string]string{
134+
"key": "value",
135+
configTag: bootstrapConfigTag,
136+
},
137+
},
138+
}, nil),
139+
instancePlugin.EXPECT().Label(instance.ID("unlabelled"), map[string]string{
140+
"key": "value",
141+
configTag: config.InstanceHash(),
142+
}).Return(nil),
143+
)
144+
145+
err := scaled.Label()
146+
147+
require.NoError(t, err)
148+
}
149+
150+
func TestUnableToLabel(t *testing.T) {
151+
ctrl := gomock.NewController(t)
152+
defer ctrl.Finish()
153+
154+
config := types.Spec{}
155+
tags := map[string]string{
156+
"key": "value",
157+
}
158+
159+
instancePlugin := mock_instance.NewMockPlugin(ctrl)
160+
scaled := &scaledGroup{
161+
settings: groupSettings{
162+
instancePlugin: instancePlugin,
163+
config: config,
164+
},
165+
memberTags: tags,
166+
}
167+
168+
gomock.InOrder(
169+
instancePlugin.EXPECT().DescribeInstances(tags).Return([]instance.Description{
170+
{
171+
ID: instance.ID("labbeled"),
172+
Tags: map[string]string{
173+
"key": "value",
174+
configTag: config.InstanceHash(),
175+
},
176+
},
177+
{
178+
ID: instance.ID("unlabelled"),
179+
Tags: map[string]string{
180+
"key": "value",
181+
configTag: bootstrapConfigTag,
182+
},
183+
},
184+
}, nil),
185+
instancePlugin.EXPECT().Label(instance.ID("unlabelled"), map[string]string{
186+
"key": "value",
187+
configTag: config.InstanceHash(),
188+
}).Return(errors.New("BUG")),
189+
)
190+
191+
err := scaled.Label()
192+
193+
require.Error(t, err)
194+
}

pkg/plugin/group/scaler.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package group
22

33
import (
44
"fmt"
5-
log "github.com/Sirupsen/logrus"
6-
"github.com/docker/infrakit/pkg/spi/instance"
75
"sort"
86
"sync"
97
"time"
8+
9+
log "github.com/Sirupsen/logrus"
10+
"github.com/docker/infrakit/pkg/spi/instance"
1011
)
1112

1213
type scaler struct {
@@ -221,10 +222,24 @@ func (s *scaler) waitIfReachParallelLimit(current int, batch *sync.WaitGroup) {
221222
func (s *scaler) converge() {
222223
descriptions, err := s.scaled.List()
223224
if err != nil {
224-
log.Errorf("Failed to check size of group: %s", err)
225+
log.Errorf("Failed to list group instances: %s", err)
225226
return
226227
}
227228

229+
if needsLabel(descriptions) {
230+
err := s.scaled.Label()
231+
if err != nil {
232+
log.Errorf("Failed to label the group instances: %s", err)
233+
return
234+
}
235+
236+
descriptions, err = s.scaled.List()
237+
if err != nil {
238+
log.Errorf("Failed to list group instances: %s", err)
239+
return
240+
}
241+
}
242+
228243
log.Debugf("Found existing instances: %v", descriptions)
229244

230245
grp := sync.WaitGroup{}

0 commit comments

Comments
 (0)