forked from docker-archive/deploykit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscaled.go
218 lines (171 loc) · 5.41 KB
/
scaled.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package group
import (
"fmt"
"sync"
"github.com/docker/infrakit/pkg/spi/flavor"
"github.com/docker/infrakit/pkg/spi/group"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/docker/infrakit/pkg/types"
)
const (
bootstrapConfigTag = "bootstrap"
)
// Scaled is a collection of instances that can be scaled up and down.
type Scaled interface {
// CreateOne creates a single instance in the scaled group. Parameters may be provided to customize behavior
// of the instance.
CreateOne(id *instance.LogicalID)
// Health inspects the current health state of an instance.
Health(inst instance.Description) flavor.Health
// Destroy destroys a single instance.
Destroy(inst instance.Description, ctx instance.Context) error
// List returns all instances in the group.
List() ([]instance.Description, error)
// Label makes sure all instances in the group are labelled.
Label() error
}
type scaledGroup struct {
supervisor Supervisor
scaler *scaler
settings groupSettings
memberTags map[string]string
lock sync.Mutex
}
func (s *scaledGroup) changeSettings(settings groupSettings) {
s.lock.Lock()
defer s.lock.Unlock()
s.settings = settings
}
// latestSettings gives a point-in-time view of the settings for this group. This allows other functions to
// safely use settings and make calls to plugins without holding the lock.
func (s *scaledGroup) latestSettings() groupSettings {
s.lock.Lock()
defer s.lock.Unlock()
return s.settings
}
func (s *scaledGroup) CreateOne(logicalID *instance.LogicalID) {
settings := s.latestSettings()
tags := map[string]string{}
for k, v := range s.memberTags {
tags[k] = v
}
if logicalID != nil {
tags[instance.LogicalIDTag] = string(*logicalID)
}
// Instances are tagged with a SHA of the entire instance configuration to support change detection.
tags[group.ConfigSHATag] = settings.config.InstanceHash()
spec := instance.Spec{
Tags: tags,
LogicalID: logicalID,
Properties: types.AnyCopy(settings.config.Instance.Properties),
}
index := group.Index{
Group: s.supervisor.ID(),
Sequence: s.supervisor.Size(),
}
spec, err := settings.flavorPlugin.Prepare(types.AnyCopy(settings.config.Flavor.Properties),
spec,
settings.config.Allocation,
index)
if err != nil {
log.Error("Failed to Prepare instance", "settings", settings, "err", err)
return
}
id, err := settings.instancePlugin.Provision(spec)
if err != nil {
log.Error("Failed to provision", "settings", settings, "err", err)
return
}
volumeDesc := ""
if len(spec.Attachments) > 0 {
volumeDesc = fmt.Sprintf(" and attachments %s", spec.Attachments)
}
log.Info("Created instance", "id", *id, "tags", spec.Tags, "volumeDesc", volumeDesc)
}
func (s *scaledGroup) Health(inst instance.Description) flavor.Health {
settings := s.latestSettings()
health, err := settings.flavorPlugin.Healthy(types.AnyCopy(settings.config.Flavor.Properties), inst)
if err != nil {
log.Warn("Failed to check health of instance", "id", inst.ID, "err", err)
return flavor.Unknown
}
return health
}
func (s *scaledGroup) Destroy(inst instance.Description, ctx instance.Context) error {
settings := s.latestSettings()
flavorProperties := types.AnyCopy(settings.config.Flavor.Properties)
if err := settings.flavorPlugin.Drain(flavorProperties, inst); err != nil {
log.Error("Failed to drain", "id", inst.ID, "err", err)
return err
}
log.Info("Destroying instance", "id", inst.ID)
if err := settings.instancePlugin.Destroy(inst.ID, ctx); err != nil {
log.Error("Failed to destroy instance", "id", inst.ID, "err", err)
return err
}
return nil
}
func (s *scaledGroup) List() ([]instance.Description, error) {
settings := s.latestSettings()
list := []instance.Description{}
found, err := settings.instancePlugin.DescribeInstances(s.memberTags, true)
if err != nil {
return list, err
}
// normalize the data. we make sure if there are logical ID in the labels,
// we also have the LogicalID field populated.
for _, d := range found {
// Is there a tag for the logical ID and the logicalID field is not set?
if logicalIDString, has := d.Tags[instance.LogicalIDTag]; has && d.LogicalID == nil {
logicalID := instance.LogicalID(logicalIDString)
d.LogicalID = &logicalID
}
list = append(list, d)
}
return list, nil
}
func (s *scaledGroup) Label() error {
settings := s.latestSettings()
instances, err := settings.instancePlugin.DescribeInstances(s.memberTags, false)
if err != nil {
return err
}
tagsWithConfigSha := map[string]string{}
for k, v := range s.memberTags {
tagsWithConfigSha[k] = v
}
tagsWithConfigSha[group.ConfigSHATag] = settings.config.InstanceHash()
for _, inst := range instances {
if instanceNeedsLabel(inst) {
log.Info("Labelling instance", "id", inst.ID)
if err := settings.instancePlugin.Label(inst.ID, tagsWithConfigSha); err != nil {
return err
}
}
}
return nil
}
func labelAndList(scaled Scaled) ([]instance.Description, error) {
descriptions, err := scaled.List()
if err != nil {
return nil, err
}
if !needsLabel(descriptions) {
return descriptions, nil
}
if err := scaled.Label(); err != nil {
return nil, err
}
return scaled.List()
}
func needsLabel(instances []instance.Description) bool {
for _, inst := range instances {
if instanceNeedsLabel(inst) {
return true
}
}
return false
}
func instanceNeedsLabel(instance instance.Description) bool {
return instance.Tags[group.ConfigSHATag] == bootstrapConfigTag
}