Browse Source

Add --force to node removal

Signed-off-by: Diogo Monica <diogo.monica@gmail.com>
(cherry picked from commit a327c231b5c68c13b7dcde2fdc83b8e4cec59c43)
Signed-off-by: Tibor Vass <tibor@docker.com>
Diogo Monica 9 years ago
parent
commit
caaf53ad3e

+ 15 - 5
api/client/node/remove.go

@@ -7,26 +7,36 @@ import (
 
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types"
 	"github.com/spf13/cobra"
 )
 
+type removeOptions struct {
+	force bool
+}
+
 func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
-	return &cobra.Command{
-		Use:     "rm NODE [NODE...]",
+	opts := removeOptions{}
+
+	cmd := &cobra.Command{
+		Use:     "rm [OPTIONS] NODE [NODE...]",
 		Aliases: []string{"remove"},
 		Short:   "Remove one or more nodes from the swarm",
 		Args:    cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return runRemove(dockerCli, args)
+			return runRemove(dockerCli, args, opts)
 		},
 	}
+	flags := cmd.Flags()
+	flags.BoolVar(&opts.force, "force", false, "Force remove an active node")
+	return cmd
 }
 
-func runRemove(dockerCli *client.DockerCli, args []string) error {
+func runRemove(dockerCli *client.DockerCli, args []string, opts removeOptions) error {
 	client := dockerCli.Client()
 	ctx := context.Background()
 	for _, nodeID := range args {
-		err := client.NodeRemove(ctx, nodeID)
+		err := client.NodeRemove(ctx, nodeID, types.NodeRemoveOptions{Force: opts.force})
 		if err != nil {
 			return err
 		}

+ 1 - 1
api/server/router/swarm/backend.go

@@ -20,7 +20,7 @@ type Backend interface {
 	GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
 	GetNode(string) (types.Node, error)
 	UpdateNode(string, uint64, types.NodeSpec) error
-	RemoveNode(string) error
+	RemoveNode(string, bool) error
 	GetTasks(basictypes.TaskListOptions) ([]types.Task, error)
 	GetTask(string) (types.Task, error)
 }

+ 7 - 1
api/server/router/swarm/cluster_routes.go

@@ -219,7 +219,13 @@ func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r
 }
 
 func (sr *swarmRouter) removeNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	if err := sr.backend.RemoveNode(vars["id"]); err != nil {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	force := httputils.BoolValue(r, "force")
+
+	if err := sr.backend.RemoveNode(vars["id"], force); err != nil {
 		logrus.Errorf("Error removing node %s: %v", vars["id"], err)
 		return err
 	}

+ 2 - 2
daemon/cluster/cluster.go

@@ -1023,7 +1023,7 @@ func (c *Cluster) UpdateNode(nodeID string, version uint64, spec types.NodeSpec)
 }
 
 // RemoveNode removes a node from a cluster
-func (c *Cluster) RemoveNode(input string) error {
+func (c *Cluster) RemoveNode(input string, force bool) error {
 	c.RLock()
 	defer c.RUnlock()
 
@@ -1039,7 +1039,7 @@ func (c *Cluster) RemoveNode(input string) error {
 		return err
 	}
 
-	if _, err := c.client.RemoveNode(ctx, &swarmapi.RemoveNodeRequest{NodeID: node.ID}); err != nil {
+	if _, err := c.client.RemoveNode(ctx, &swarmapi.RemoveNodeRequest{NodeID: node.ID, Force: force}); err != nil {
 		return err
 	}
 	return nil

+ 20 - 1
docs/reference/commandline/node_rm.md

@@ -13,7 +13,7 @@ parent = "smn_cli"
 # node rm
 
 ```markdown
-Usage:  docker node rm NODE [NODE...]
+Usage:  docker node rm [OPTIONS] NODE [NODE...]
 
 Remove one or more nodes from the swarm
 
@@ -21,6 +21,7 @@ Aliases:
   rm, remove
 
 Options:
+      --force  Force remove an active node
       --help   Print usage
 ```
 
@@ -32,6 +33,24 @@ Example output:
     $ docker node rm swarm-node-02
     Node swarm-node-02 removed from swarm
 
+Removes nodes from the swarm that are in the down state. Attempting to remove
+an active node will result in an error:
+
+```bash
+$ docker node rm swarm-node-03
+Error response from daemon: rpc error: code = 9 desc = node swarm-node-03 is not down and can't be removed
+```
+
+If a worker node becomes compromised, exhibits unexpected or unwanted behavior, or if you lose access to it so
+that a clean shutdown is impossible, you can use the force option.
+
+```bash
+$ docker node rm --force swarm-node-03
+Node swarm-node-03 removed from swarm
+```
+
+Note that manager nodes have to be demoted to worker nodes before they can be removed
+from the cluster.
 
 ## Related information
 

+ 11 - 0
integration-cli/daemon_swarm.go

@@ -208,6 +208,17 @@ func (d *SwarmDaemon) getNode(c *check.C, id string) *swarm.Node {
 	return &node
 }
 
+func (d *SwarmDaemon) removeNode(c *check.C, id string, force bool) {
+	url := "/nodes/" + id
+	if force {
+		url += "?force=1"
+	}
+
+	status, out, err := d.SockRequest("DELETE", url, nil)
+	c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out)))
+	c.Assert(err, checker.IsNil)
+}
+
 func (d *SwarmDaemon) updateNode(c *check.C, id string, f ...nodeConstructor) {
 	for i := 0; ; i++ {
 		node := d.getNode(c, id)

+ 31 - 0
integration-cli/docker_api_swarm_test.go

@@ -510,6 +510,37 @@ func (s *DockerSwarmSuite) TestApiSwarmNodeUpdate(c *check.C) {
 	c.Assert(n.Spec.Availability, checker.Equals, swarm.NodeAvailabilityPause)
 }
 
+func (s *DockerSwarmSuite) TestApiSwarmNodeRemove(c *check.C) {
+	testRequires(c, Network)
+	d1 := s.AddDaemon(c, true, true)
+	d2 := s.AddDaemon(c, true, false)
+	_ = s.AddDaemon(c, true, false)
+
+	nodes := d1.listNodes(c)
+	c.Assert(len(nodes), checker.Equals, 3, check.Commentf("nodes: %#v", nodes))
+
+	// Getting the info so we can take the NodeID
+	d2Info, err := d2.info()
+	c.Assert(err, checker.IsNil)
+
+	// forceful removal of d2 should work
+	d1.removeNode(c, d2Info.NodeID, true)
+
+	nodes = d1.listNodes(c)
+	c.Assert(len(nodes), checker.Equals, 2, check.Commentf("nodes: %#v", nodes))
+
+	// Restart the node that was removed
+	err = d2.Restart()
+	c.Assert(err, checker.IsNil)
+
+	// Give some time for the node to rejoin
+	time.Sleep(1 * time.Second)
+
+	// Make sure the node didn't rejoin
+	nodes = d1.listNodes(c)
+	c.Assert(len(nodes), checker.Equals, 2, check.Commentf("nodes: %#v", nodes))
+}
+
 func (s *DockerSwarmSuite) TestApiSwarmNodeDrainPause(c *check.C) {
 	testRequires(c, Network)
 	d1 := s.AddDaemon(c, true, true)