Skip to content

Commit

Permalink
Add --force to node removal
Browse files Browse the repository at this point in the history
Signed-off-by: Diogo Monica <[email protected]>
  • Loading branch information
diogomonica committed Aug 2, 2016
1 parent 415c0f1 commit a327c23
Show file tree
Hide file tree
Showing 27 changed files with 404 additions and 206 deletions.
20 changes: 15 additions & 5 deletions api/client/node/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion api/server/router/swarm/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 7 additions & 1 deletion api/server/router/swarm/cluster_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions daemon/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
Expand Down
21 changes: 20 additions & 1 deletion docs/reference/commandline/node_rm.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ 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

Aliases:
rm, remove

Options:
--force Force remove an active node
--help Print usage
```

Expand All @@ -30,6 +31,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
Expand Down
5 changes: 3 additions & 2 deletions hack/vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ clone git golang.org/x/net 2beffdc2e92c8a3027590f898fe88f69af48a3f8 https://gith
clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git
clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d
clone git github.com/docker/engine-api 3d1601b9d2436a70b0dfc045a23f6503d19195df

clone git github.com/docker/engine-api 228c7390a733320d48697cb41ae8cde4942cd3e5
clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
clone git github.com/imdario/mergo 0.2.1

Expand Down Expand Up @@ -139,7 +140,7 @@ clone git github.com/docker/docker-credential-helpers v0.3.0
clone git github.com/docker/containerd 0ac3cd1be170d180b2baed755e8f0da547ceb267

# cluster
clone git github.com/docker/swarmkit 9d4c2f73124e70f8fa85f9076635b827d17b109f
clone git github.com/docker/swarmkit e1c0d64515d839b76e2ef33d396c74933753ffaf
clone git github.com/golang/mock bd3c8e81be01eef76d4b503f5e687d2d1354d2d9
clone git github.com/gogo/protobuf 43a2e0b1c32252bfbbdf81f7faa7a88fb3fa4028
clone git github.com/cloudflare/cfssl b895b0549c0ff676f92cf09ba971ae02bb41367b
Expand Down
11 changes: 11 additions & 0 deletions integration-cli/daemon_swarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions integration-cli/docker_api_swarm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,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) {
d1 := s.AddDaemon(c, true, true)
d2 := s.AddDaemon(c, true, false)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build linux freebsd solaris openbsd
// +build linux freebsd solaris openbsd darwin

package client

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type NetworkAPIClient interface {
type NodeAPIClient interface {
NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
NodeRemove(ctx context.Context, nodeID string) error
NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error
NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
}

Expand Down
17 changes: 14 additions & 3 deletions vendor/src/github.com/docker/engine-api/client/node_remove.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package client

import "golang.org/x/net/context"
import (
"net/url"

"github.com/docker/engine-api/types"

"golang.org/x/net/context"
)

// NodeRemove removes a Node.
func (cli *Client) NodeRemove(ctx context.Context, nodeID string) error {
resp, err := cli.delete(ctx, "/nodes/"+nodeID, nil, nil)
func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error {
query := url.Values{}
if options.Force {
query.Set("force", "1")
}

resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
ensureReaderClosed(resp)
return err
}
7 changes: 6 additions & 1 deletion vendor/src/github.com/docker/engine-api/types/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,16 @@ func (v VersionResponse) ServerOK() bool {
return v.Server != nil
}

// NodeListOptions holds parameters to list nodes with.
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filter filters.Args
}

// NodeRemoveOptions holds parameters to remove nodes with.
type NodeRemoveOptions struct {
Force bool
}

// ServiceCreateOptions contains the options to use when creating a service.
type ServiceCreateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
Expand Down
4 changes: 2 additions & 2 deletions vendor/src/github.com/docker/swarmkit/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import (
)

const (
initialSessionFailureBackoff = time.Second
initialSessionFailureBackoff = 100 * time.Millisecond
maxSessionFailureBackoff = 8 * time.Second
)

// Agent implements the primary node functionality for a member of a swarm
// cluster. The primary functionality id to run and report on the status of
// cluster. The primary functionality is to run and report on the status of
// tasks assigned to the node.
type Agent struct {
config *Config
Expand Down
4 changes: 2 additions & 2 deletions vendor/src/github.com/docker/swarmkit/agent/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (n *Node) run(ctx context.Context) (err error) {
if n.config.JoinAddr != "" || n.config.ForceNewCluster {
n.remotes = newPersistentRemotes(filepath.Join(n.config.StateDir, stateFilename))
if n.config.JoinAddr != "" {
n.remotes.Observe(api.Peer{Addr: n.config.JoinAddr}, 1)
n.remotes.Observe(api.Peer{Addr: n.config.JoinAddr}, picker.DefaultObservationWeight)
}
}

Expand Down Expand Up @@ -647,7 +647,7 @@ func (n *Node) runManager(ctx context.Context, securityConfig *ca.SecurityConfig
go func(ready chan struct{}) {
select {
case <-ready:
n.remotes.Observe(api.Peer{NodeID: n.nodeID, Addr: n.config.ListenRemoteAPI}, 5)
n.remotes.Observe(api.Peer{NodeID: n.nodeID, Addr: n.config.ListenRemoteAPI}, picker.DefaultObservationWeight)
case <-connCtx.Done():
}
}(ready)
Expand Down
2 changes: 1 addition & 1 deletion vendor/src/github.com/docker/swarmkit/agent/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (tm *taskManager) run(ctx context.Context) {
cancel() // cancel outstanding if necessary.
} else {
// If this channel op fails, it means there is already a
// message un the run queue.
// message on the run queue.
select {
case run <- struct{}{}:
default:
Expand Down
Loading

0 comments on commit a327c23

Please sign in to comment.