Browse code

Allow swarm join with `--availability=drain` This fix tries to address the issue raised in 24596 where it was not possible to join as manager only (`--availability=drain`).

This fix adds a new flag `--availability` to `swarm join`.

Related documentation has been updated.

An integration test has been added.

NOTE: Additional pull request for swarmkit and engine-api will
be created separately.

This fix fixes 24596.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/12/22 11:06:16
Showing 7 changed files
... ...
@@ -143,6 +143,7 @@ type JoinRequest struct {
143 143
 	AdvertiseAddr string
144 144
 	RemoteAddrs   []string
145 145
 	JoinToken     string // accept by secret
146
+	Availability  NodeAvailability
146 147
 }
147 148
 
148 149
 // UnlockRequest is the request used to unlock a swarm.
... ...
@@ -2,12 +2,15 @@ package swarm
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"strings"
6
+
7
+	"golang.org/x/net/context"
5 8
 
6 9
 	"github.com/docker/docker/api/types/swarm"
7 10
 	"github.com/docker/docker/cli"
8 11
 	"github.com/docker/docker/cli/command"
9 12
 	"github.com/spf13/cobra"
10
-	"golang.org/x/net/context"
13
+	"github.com/spf13/pflag"
11 14
 )
12 15
 
13 16
 type joinOptions struct {
... ...
@@ -16,6 +19,7 @@ type joinOptions struct {
16 16
 	// Not a NodeAddrOption because it has no default port.
17 17
 	advertiseAddr string
18 18
 	token         string
19
+	availability  string
19 20
 }
20 21
 
21 22
 func newJoinCommand(dockerCli command.Cli) *cobra.Command {
... ...
@@ -29,7 +33,7 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
29 29
 		Args:  cli.ExactArgs(1),
30 30
 		RunE: func(cmd *cobra.Command, args []string) error {
31 31
 			opts.remote = args[0]
32
-			return runJoin(dockerCli, opts)
32
+			return runJoin(dockerCli, cmd.Flags(), opts)
33 33
 		},
34 34
 	}
35 35
 
... ...
@@ -37,10 +41,11 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
37 37
 	flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
38 38
 	flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
39 39
 	flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm")
40
+	flags.StringVar(&opts.availability, flagAvailability, "active", "Availability of the node (active/pause/drain)")
40 41
 	return cmd
41 42
 }
42 43
 
43
-func runJoin(dockerCli command.Cli, opts joinOptions) error {
44
+func runJoin(dockerCli command.Cli, flags *pflag.FlagSet, opts joinOptions) error {
44 45
 	client := dockerCli.Client()
45 46
 	ctx := context.Background()
46 47
 
... ...
@@ -50,6 +55,16 @@ func runJoin(dockerCli command.Cli, opts joinOptions) error {
50 50
 		AdvertiseAddr: opts.advertiseAddr,
51 51
 		RemoteAddrs:   []string{opts.remote},
52 52
 	}
53
+	if flags.Changed(flagAvailability) {
54
+		availability := swarm.NodeAvailability(strings.ToLower(opts.availability))
55
+		switch availability {
56
+		case swarm.NodeAvailabilityActive, swarm.NodeAvailabilityPause, swarm.NodeAvailabilityDrain:
57
+			req.Availability = availability
58
+		default:
59
+			return fmt.Errorf("invalid availability %q, only active, pause and drain are supported", opts.availability)
60
+		}
61
+	}
62
+
53 63
 	err := client.SwarmJoin(ctx, req)
54 64
 	if err != nil {
55 65
 		return err
... ...
@@ -28,6 +28,7 @@ const (
28 28
 	flagSnapshotInterval    = "snapshot-interval"
29 29
 	flagLockKey             = "lock-key"
30 30
 	flagAutolock            = "autolock"
31
+	flagAvailability        = "availability"
31 32
 )
32 33
 
33 34
 type swarmOptions struct {
... ...
@@ -389,6 +389,7 @@ func (c *Cluster) Join(req types.JoinRequest) error {
389 389
 		AdvertiseAddr: advertiseAddr,
390 390
 		joinAddr:      req.RemoteAddrs[0],
391 391
 		joinToken:     req.JoinToken,
392
+		availability:  req.Availability,
392 393
 	})
393 394
 	if err != nil {
394 395
 		return err
... ...
@@ -1,6 +1,7 @@
1 1
 package cluster
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"path/filepath"
5 6
 	"runtime"
6 7
 	"strings"
... ...
@@ -51,6 +52,7 @@ type nodeStartConfig struct {
51 51
 	joinToken       string
52 52
 	lockKey         []byte
53 53
 	autolock        bool
54
+	availability    types.NodeAvailability
54 55
 }
55 56
 
56 57
 func (n *nodeRunner) Ready() chan error {
... ...
@@ -92,7 +94,7 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
92 92
 		control = filepath.Join(n.cluster.runtimeRoot, controlSocket)
93 93
 	}
94 94
 
95
-	node, err := swarmnode.New(&swarmnode.Config{
95
+	swarmnodeConfig := swarmnode.Config{
96 96
 		Hostname:           n.cluster.config.Name,
97 97
 		ForceNewCluster:    conf.forceNewCluster,
98 98
 		ListenControlAPI:   control,
... ...
@@ -106,7 +108,15 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
106 106
 		ElectionTick:       3,
107 107
 		UnlockKey:          conf.lockKey,
108 108
 		AutoLockManagers:   conf.autolock,
109
-	})
109
+	}
110
+	if conf.availability != "" {
111
+		avail, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(conf.availability))]
112
+		if !ok {
113
+			return fmt.Errorf("invalid Availability: %q", conf.availability)
114
+		}
115
+		swarmnodeConfig.Availability = swarmapi.NodeSpec_Availability(avail)
116
+	}
117
+	node, err := swarmnode.New(&swarmnodeConfig)
110 118
 	if err != nil {
111 119
 		return err
112 120
 	}
... ...
@@ -22,6 +22,7 @@ Join a swarm as a node and/or manager
22 22
 
23 23
 Options:
24 24
       --advertise-addr string   Advertised address (format: <ip|interface>[:port])
25
+      --availability string     Availability of the node (active/pause/drain) (default "active")
25 26
       --help                    Print usage
26 27
       --listen-addr node-addr   Listen address (format: <ip|interface>[:port]) (default 0.0.0.0:2377)
27 28
       --token string            Token for entry into the swarm
... ...
@@ -94,6 +95,15 @@ This flag is generally not necessary when joining an existing swarm.
94 94
 
95 95
 Secret value required for nodes to join the swarm
96 96
 
97
+### `--availability`
98
+
99
+This flag specifies the availability of the node at the time the node joins a master.
100
+Possible availability values are `active`, `pause`, or `drain`.
101
+
102
+This flag is useful in certain situations. For example, a cluster may want to have
103
+dedicated manager nodes that are not served as worker nodes. This could be achieved
104
+by passing `--availability=drain` to `docker swarm join`.
105
+
97 106
 
98 107
 ## Related information
99 108
 
... ...
@@ -1591,3 +1591,31 @@ func (s *DockerSwarmSuite) TestSwarmPublishDuplicatePorts(c *check.C) {
1591 1591
 	c.Assert(out, checker.Contains, "{ tcp 80 5000 ingress}")
1592 1592
 	c.Assert(out, checker.Contains, "{ tcp 80 5001 ingress}")
1593 1593
 }
1594
+
1595
+func (s *DockerSwarmSuite) TestSwarmJoinWithDrain(c *check.C) {
1596
+	d := s.AddDaemon(c, true, true)
1597
+
1598
+	out, err := d.Cmd("node", "ls")
1599
+	c.Assert(err, checker.IsNil)
1600
+	c.Assert(out, checker.Not(checker.Contains), "Drain")
1601
+
1602
+	out, err = d.Cmd("swarm", "join-token", "-q", "manager")
1603
+	c.Assert(err, checker.IsNil)
1604
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
1605
+
1606
+	token := strings.TrimSpace(out)
1607
+
1608
+	d1 := s.AddDaemon(c, false, false)
1609
+
1610
+	out, err = d1.Cmd("swarm", "join", "--availability=drain", "--token", token, d.ListenAddr)
1611
+	c.Assert(err, checker.IsNil)
1612
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
1613
+
1614
+	out, err = d.Cmd("node", "ls")
1615
+	c.Assert(err, checker.IsNil)
1616
+	c.Assert(out, checker.Contains, "Drain")
1617
+
1618
+	out, err = d1.Cmd("node", "ls")
1619
+	c.Assert(err, checker.IsNil)
1620
+	c.Assert(out, checker.Contains, "Drain")
1621
+}