@@ -11,33 +11,22 @@ import (
11
11
"github.com/docker/docker/api/types/filters"
12
12
"github.com/docker/docker/client"
13
13
"github.com/docker/infrakit/pkg/plugin/group/types"
14
- "github.com/docker/infrakit/pkg/plugin/group/util"
15
14
"github.com/docker/infrakit/pkg/spi/flavor"
16
15
"github.com/docker/infrakit/pkg/spi/instance"
17
16
"github.com/docker/infrakit/pkg/template"
18
17
"golang.org/x/net/context"
19
18
)
20
19
21
- type nodeType string
22
-
23
20
const (
24
- worker nodeType = "worker"
25
- manager nodeType = "manager"
26
- ebsAttachment string = "ebs"
21
+ ebsAttachment string = "ebs"
27
22
)
28
23
29
- // NewSwarmFlavor creates a flavor.Plugin that creates manager and worker nodes connected in a swarm.
30
- func NewSwarmFlavor (dockerClient client.APIClient , templ * template.Template ) flavor.Plugin {
31
- return & swarmFlavor {client : dockerClient , initScript : templ }
32
- }
33
-
34
24
type swarmFlavor struct {
35
25
client client.APIClient
36
26
initScript * template.Template
37
27
}
38
28
39
29
type schema struct {
40
- Type nodeType
41
30
Attachments map [instance.LogicalID ][]instance.Attachment
42
31
DockerRestartCommand string
43
32
}
@@ -48,7 +37,9 @@ func parseProperties(flavorProperties json.RawMessage) (schema, error) {
48
37
return s , err
49
38
}
50
39
51
- func validateIDsAndAttachments (logicalIDs []instance.LogicalID , attachments map [instance.LogicalID ][]instance.Attachment ) error {
40
+ func validateIDsAndAttachments (logicalIDs []instance.LogicalID ,
41
+ attachments map [instance.LogicalID ][]instance.Attachment ) error {
42
+
52
43
// Each attachment association must be represented by a logical ID.
53
44
idsMap := map [instance.LogicalID ]bool {}
54
45
for _ , id := range logicalIDs {
@@ -97,49 +88,14 @@ func validateIDsAndAttachments(logicalIDs []instance.LogicalID, attachments map[
97
88
return nil
98
89
}
99
90
100
- func (s swarmFlavor ) Validate (flavorProperties json.RawMessage , allocation types.AllocationMethod ) error {
101
- properties , err := parseProperties (flavorProperties )
102
- if err != nil {
103
- return err
104
- }
105
-
106
- if properties .DockerRestartCommand == "" {
107
- return errors .New ("DockerRestartCommand must be specified" )
108
- }
109
-
110
- switch properties .Type {
111
- case manager :
112
- numIDs := len (allocation .LogicalIDs )
113
- if numIDs != 1 && numIDs != 3 && numIDs != 5 {
114
- return errors .New ("Must have 1, 3, or 5 manager logical IDs" )
115
- }
116
- case worker :
117
-
118
- default :
119
- return errors .New ("Unrecognized node Type" )
120
- }
121
-
122
- if properties .Type == manager {
123
- for _ , id := range allocation .LogicalIDs {
124
- if att , exists := properties .Attachments [id ]; ! exists || len (att ) == 0 {
125
- log .Warnf ("LogicalID %s has no attachments, which is needed for durability" , id )
126
- }
127
- }
128
- }
129
-
130
- if err := validateIDsAndAttachments (allocation .LogicalIDs , properties .Attachments ); err != nil {
131
- return err
132
- }
133
-
134
- return nil
135
- }
136
-
137
91
const (
138
92
// associationTag is a machine tag added to associate machines with Swarm nodes.
139
93
associationTag = "swarm-association-id"
140
94
)
141
95
142
- func generateInitScript (templ * template.Template , joinIP , joinToken , associationID , restartCommand string ) (string , error ) {
96
+ func generateInitScript (templ * template.Template ,
97
+ joinIP , joinToken , associationID , restartCommand string ) (string , error ) {
98
+
143
99
var buffer bytes.Buffer
144
100
err := templ .Execute (& buffer , map [string ]string {
145
101
"MY_IP" : joinIP ,
@@ -153,9 +109,25 @@ func generateInitScript(templ *template.Template, joinIP, joinToken, association
153
109
return buffer .String (), nil
154
110
}
155
111
112
+ func (s swarmFlavor ) Validate (flavorProperties json.RawMessage , allocation types.AllocationMethod ) error {
113
+ properties , err := parseProperties (flavorProperties )
114
+ if err != nil {
115
+ return err
116
+ }
117
+ if properties .DockerRestartCommand == "" {
118
+ return errors .New ("DockerRestartCommand must be specified" )
119
+ }
120
+ if err := validateIDsAndAttachments (allocation .LogicalIDs , properties .Attachments ); err != nil {
121
+ return err
122
+ }
123
+ return nil
124
+ }
125
+
156
126
// Healthy determines whether an instance is healthy. This is determined by whether it has successfully joined the
157
127
// Swarm.
158
- func (s swarmFlavor ) Healthy (flavorProperties json.RawMessage , inst instance.Description ) (flavor.Health , error ) {
128
+ func healthy (client client.APIClient ,
129
+ flavorProperties json.RawMessage , inst instance.Description ) (flavor.Health , error ) {
130
+
159
131
associationID , exists := inst .Tags [associationTag ]
160
132
if ! exists {
161
133
log .Info ("Reporting unhealthy for instance without an association tag" , inst .ID )
@@ -165,7 +137,7 @@ func (s swarmFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Des
165
137
filter := filters .NewArgs ()
166
138
filter .Add ("label" , fmt .Sprintf ("%s=%s" , associationTag , associationID ))
167
139
168
- nodes , err := s . client .NodeList (context .Background (), docker_types.NodeListOptions {Filters : filter })
140
+ nodes , err := client .NodeList (context .Background (), docker_types.NodeListOptions {Filters : filter })
169
141
if err != nil {
170
142
return flavor .Unknown , err
171
143
}
@@ -183,128 +155,3 @@ func (s swarmFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Des
183
155
return flavor .Healthy , nil
184
156
}
185
157
}
186
-
187
- func (s swarmFlavor ) Drain (flavorProperties json.RawMessage , inst instance.Description ) error {
188
- properties , err := parseProperties (flavorProperties )
189
- if err != nil {
190
- return err
191
- }
192
-
193
- // Only explicitly remove worker nodes, not manager nodes. Manager nodes are assumed to have an
194
- // attached volume for state, and fixed IP addresses. This allows them to rejoin as the same node.
195
- if properties .Type != worker {
196
- return nil
197
- }
198
-
199
- associationID , exists := inst .Tags [associationTag ]
200
- if ! exists {
201
- return fmt .Errorf ("Unable to drain %s without an association tag" , inst .ID )
202
- }
203
-
204
- filter := filters .NewArgs ()
205
- filter .Add ("label" , fmt .Sprintf ("%s=%s" , associationTag , associationID ))
206
-
207
- nodes , err := s .client .NodeList (context .Background (), docker_types.NodeListOptions {Filters : filter })
208
- if err != nil {
209
- return err
210
- }
211
-
212
- switch {
213
- case len (nodes ) == 0 :
214
- return fmt .Errorf ("Unable to drain %s, not found in swarm" , inst .ID )
215
-
216
- case len (nodes ) == 1 :
217
- err := s .client .NodeRemove (
218
- context .Background (),
219
- nodes [0 ].ID ,
220
- docker_types.NodeRemoveOptions {Force : true })
221
- if err != nil {
222
- return err
223
- }
224
-
225
- return nil
226
-
227
- default :
228
- return fmt .Errorf ("Expected at most one node with label %s, but found %s" , associationID , nodes )
229
- }
230
- }
231
-
232
- func (s * swarmFlavor ) Prepare (
233
- flavorProperties json.RawMessage ,
234
- spec instance.Spec ,
235
- allocation types.AllocationMethod ) (instance.Spec , error ) {
236
-
237
- properties , err := parseProperties (flavorProperties )
238
- if err != nil {
239
- return spec , err
240
- }
241
-
242
- swarmStatus , err := s .client .SwarmInspect (context .Background ())
243
- if err != nil {
244
- return spec , fmt .Errorf ("Failed to fetch Swarm join tokens: %s" , err )
245
- }
246
-
247
- nodeInfo , err := s .client .Info (context .Background ())
248
- if err != nil {
249
- return spec , fmt .Errorf ("Failed to fetch node self info: %s" , err )
250
- }
251
-
252
- self , _ , err := s .client .NodeInspectWithRaw (context .Background (), nodeInfo .Swarm .NodeID )
253
- if err != nil {
254
- return spec , fmt .Errorf ("Failed to fetch Swarm node status: %s" , err )
255
- }
256
-
257
- if self .ManagerStatus == nil {
258
- return spec , errors .New (
259
- "Swarm node status did not include manager status. Need to run 'docker swarm init`?" )
260
- }
261
-
262
- associationID := util .RandomAlphaNumericString (8 )
263
- spec .Tags [associationTag ] = associationID
264
-
265
- switch properties .Type {
266
- case worker :
267
- initScript , err :=
268
- generateInitScript (
269
- s .initScript ,
270
- self .ManagerStatus .Addr ,
271
- swarmStatus .JoinTokens .Worker ,
272
- associationID ,
273
- properties .DockerRestartCommand )
274
- if err != nil {
275
- return spec , err
276
- }
277
- spec .Init = initScript
278
-
279
- case manager :
280
- if spec .LogicalID == nil {
281
- return spec , errors .New ("Manager nodes require a LogicalID, " +
282
- "which will be used as an assigned private IP address" )
283
- }
284
-
285
- initScript , err := generateInitScript (
286
- s .initScript ,
287
- self .ManagerStatus .Addr ,
288
- swarmStatus .JoinTokens .Manager ,
289
- associationID ,
290
- properties .DockerRestartCommand )
291
- if err != nil {
292
- return spec , err
293
- }
294
- spec .Init = initScript
295
- default :
296
- return spec , errors .New ("Unsupported node type" )
297
- }
298
-
299
- if spec .LogicalID != nil {
300
- if attachments , exists := properties .Attachments [* spec .LogicalID ]; exists {
301
- spec .Attachments = append (spec .Attachments , attachments ... )
302
- }
303
- }
304
-
305
- // TODO(wfarner): Use the cluster UUID to scope instances for this swarm separately from instances in another
306
- // swarm. This will require plumbing back to Scaled (membership tags).
307
- spec .Tags ["swarm-id" ] = swarmStatus .ID
308
-
309
- return spec , nil
310
- }
0 commit comments