@@ -11,33 +11,22 @@ import (
1111 "github.com/docker/docker/api/types/filters"
1212 "github.com/docker/docker/client"
1313 "github.com/docker/infrakit/pkg/plugin/group/types"
14- "github.com/docker/infrakit/pkg/plugin/group/util"
1514 "github.com/docker/infrakit/pkg/spi/flavor"
1615 "github.com/docker/infrakit/pkg/spi/instance"
1716 "github.com/docker/infrakit/pkg/template"
1817 "golang.org/x/net/context"
1918)
2019
21- type nodeType string
22-
2320const (
24- worker nodeType = "worker"
25- manager nodeType = "manager"
26- ebsAttachment string = "ebs"
21+ ebsAttachment string = "ebs"
2722)
2823
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-
3424type swarmFlavor struct {
3525 client client.APIClient
3626 initScript * template.Template
3727}
3828
3929type schema struct {
40- Type nodeType
4130 Attachments map [instance.LogicalID ][]instance.Attachment
4231 DockerRestartCommand string
4332}
@@ -48,7 +37,9 @@ func parseProperties(flavorProperties json.RawMessage) (schema, error) {
4837 return s , err
4938}
5039
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+
5243 // Each attachment association must be represented by a logical ID.
5344 idsMap := map [instance.LogicalID ]bool {}
5445 for _ , id := range logicalIDs {
@@ -97,49 +88,14 @@ func validateIDsAndAttachments(logicalIDs []instance.LogicalID, attachments map[
9788 return nil
9889}
9990
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-
13791const (
13892 // associationTag is a machine tag added to associate machines with Swarm nodes.
13993 associationTag = "swarm-association-id"
14094)
14195
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+
14399 var buffer bytes.Buffer
144100 err := templ .Execute (& buffer , map [string ]string {
145101 "MY_IP" : joinIP ,
@@ -153,9 +109,25 @@ func generateInitScript(templ *template.Template, joinIP, joinToken, association
153109 return buffer .String (), nil
154110}
155111
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+
156126// Healthy determines whether an instance is healthy. This is determined by whether it has successfully joined the
157127// 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+
159131 associationID , exists := inst .Tags [associationTag ]
160132 if ! exists {
161133 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
165137 filter := filters .NewArgs ()
166138 filter .Add ("label" , fmt .Sprintf ("%s=%s" , associationTag , associationID ))
167139
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 })
169141 if err != nil {
170142 return flavor .Unknown , err
171143 }
@@ -183,128 +155,3 @@ func (s swarmFlavor) Healthy(flavorProperties json.RawMessage, inst instance.Des
183155 return flavor .Healthy , nil
184156 }
185157}
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