diff --git a/agent/agent.go b/agent/agent.go index d7d4c71cd0..7922900f54 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1782,6 +1782,9 @@ func (a *Agent) JoinWAN(addrs []string) (n int, err error) { // ForceLeave is used to remove a failed node from the cluster func (a *Agent) ForceLeave(node string, prune bool) (err error) { a.logger.Printf("[INFO] agent: Force leaving node: %v", node) + if ok := a.IsMember(node); !ok { + return fmt.Errorf("agent: No node found with name '%s'", node) + } err = a.delegate.RemoveFailedNode(node, prune) if err != nil { a.logger.Printf("[WARN] agent: Failed to remove node: %v", err) @@ -1807,6 +1810,18 @@ func (a *Agent) WANMembers() []serf.Member { return nil } +// IsMember is used to check if a node with the given nodeName +// is a member +func (a *Agent) IsMember(nodeName string) bool { + for _, m := range a.LANMembers() { + if m.Name == nodeName { + return true + } + } + + return false +} + // StartSync is called once Services and Checks are registered. // This is called to prevent a race between clients and the anti-entropy routines func (a *Agent) StartSync() { diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index f0e34f64d6..6437b7c15f 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -1632,15 +1632,17 @@ func TestAgent_ForceLeave_ACLDeny(t *testing.T) { defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") + uri := fmt.Sprintf("/v1/agent/force-leave/%s", a.Config.NodeName) + t.Run("no token", func(t *testing.T) { - req, _ := http.NewRequest("PUT", "/v1/agent/force-leave/nope", nil) + req, _ := http.NewRequest("PUT", uri, nil) if _, err := a.srv.AgentForceLeave(nil, req); !acl.IsErrPermissionDenied(err) { t.Fatalf("err: %v", err) } }) t.Run("agent master token", func(t *testing.T) { - req, _ := http.NewRequest("PUT", "/v1/agent/force-leave/nope?token=towel", nil) + req, _ := http.NewRequest("PUT", uri+"?token=towel", nil) if _, err := a.srv.AgentForceLeave(nil, req); err != nil { t.Fatalf("err: %v", err) } @@ -1648,7 +1650,7 @@ func TestAgent_ForceLeave_ACLDeny(t *testing.T) { t.Run("read-only token", func(t *testing.T) { ro := makeReadOnlyAgentACL(t, a.srv) - req, _ := http.NewRequest("PUT", fmt.Sprintf("/v1/agent/force-leave/nope?token=%s", ro), nil) + req, _ := http.NewRequest("PUT", fmt.Sprintf(uri+"?token=%s", ro), nil) if _, err := a.srv.AgentForceLeave(nil, req); !acl.IsErrPermissionDenied(err) { t.Fatalf("err: %v", err) } diff --git a/api/agent_test.go b/api/agent_test.go index 2d2bb30a0c..ccec5c1427 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -1073,7 +1073,7 @@ func TestAPI_AgentForceLeave(t *testing.T) { agent := c.Agent() // Eject somebody - err := agent.ForceLeave("foo") + err := agent.ForceLeave(s.Config.NodeName) if err != nil { t.Fatalf("err: %v", err) } @@ -1087,7 +1087,7 @@ func TestAPI_AgentForceLeavePrune(t *testing.T) { agent := c.Agent() // Eject somebody - err := agent.ForceLeavePrune("foo") + err := agent.ForceLeavePrune(s.Config.NodeName) if err != nil { t.Fatalf("err: %v", err) } diff --git a/command/forceleave/forceleave_test.go b/command/forceleave/forceleave_test.go index eb923fa517..ce06bcfcd7 100644 --- a/command/forceleave/forceleave_test.go +++ b/command/forceleave/forceleave_test.go @@ -56,6 +56,24 @@ func TestForceLeaveCommand(t *testing.T) { }) } +func TestForceLeaveCommand_NoNodeWithName(t *testing.T) { + t.Parallel() + a1 := agent.NewTestAgent(t, t.Name(), ``) + defer a1.Shutdown() + + ui := cli.NewMockUi() + c := New(ui) + args := []string{ + "-http-addr=" + a1.HTTPAddr(), + "garbage-name", + } + + code := c.Run(args) + if code != 1 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } +} + func TestForceLeaveCommand_prune(t *testing.T) { t.Parallel() a1 := agent.NewTestAgent(t, t.Name()+"-a1", ``)