Add --force to node removal
Signed-off-by: Diogo Monica <diogo.monica@gmail.com>
(cherry picked from commit a327c231b5
)
Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
parent
b32462e2a4
commit
caaf53ad3e
7 changed files with 87 additions and 10 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue