From c5277224c74c64f2083592e429ac607eb5da005f Mon Sep 17 00:00:00 2001 From: James Rasell Date: Tue, 11 Aug 2020 17:43:12 +0100 Subject: [PATCH] api: add node purge SDK function. --- api/nodes.go | 34 +++++++++++++++++++++++++++++++++- api/nodes_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/api/nodes.go b/api/nodes.go index 8ec6f8d0ab9..55c335ae771 100644 --- a/api/nodes.go +++ b/api/nodes.go @@ -14,7 +14,7 @@ const ( NodeStatusDown = "down" // NodeSchedulingEligible and Ineligible marks the node as eligible or not, - // respectively, for receiving allocations. This is orthoginal to the node + // respectively, for receiving allocations. This is orthogonal to the node // status being ready. NodeSchedulingEligible = "eligible" NodeSchedulingIneligible = "ineligible" @@ -435,6 +435,38 @@ func (n *Nodes) GcAlloc(allocID string, q *QueryOptions) error { return err } +// Purge removes a node from the system. Nodes can still re-join the cluster if +// they are alive. +func (n *Nodes) Purge(nodeID string, q *QueryOptions) (*NodePurgeResponse, *QueryMeta, error) { + var resp NodePurgeResponse + path := fmt.Sprintf("/v1/node/%s/purge", nodeID) + qm, err := n.client.putQuery(path, nil, &resp, q) + if err != nil { + return nil, nil, err + } + return &resp, qm, nil +} + +// NodePurgeResponse is used to deserialize a Purge response. +type NodePurgeResponse struct { + HeartbeatTTL time.Duration + EvalIDs []string + EvalCreateIndex uint64 + NodeModifyIndex uint64 + LeaderRPCAddr string + NumNodes int32 + Servers []*NodeServerInfo +} + +// NodeServerInfo is used within NodePurgeResponse to deserialize a Purge +// response. +type NodeServerInfo struct { + Datacenter string + RPCAdvertiseAddr string + RPCMajorVersion int32 + RPCMinorVersion int32 +} + // DriverInfo is used to deserialize a DriverInfo entry type DriverInfo struct { Attributes map[string]string diff --git a/api/nodes_test.go b/api/nodes_test.go index 4bf8c41b2ea..49b9c1e956f 100644 --- a/api/nodes_test.go +++ b/api/nodes_test.go @@ -506,6 +506,47 @@ func TestNodes_DrainStrategy_Equal(t *testing.T) { require.True(d.Equal(o)) } +func TestNodes_Purge(t *testing.T) { + t.Parallel() + require := require.New(t) + c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) { + c.DevMode = true + }) + defer s.Stop() + + // Purge on a nonexistent node fails. + _, _, err := c.Nodes().Purge("12345678-abcd-efab-cdef-123456789abc", nil) + if err == nil || !strings.Contains(err.Error(), "not found") { + t.Fatalf("expected not found error, got: %#v", err) + } + + // Wait for node registration and get the ID so we can attempt to purge a + // node that exists. + var nodeID string + testutil.WaitForResult(func() (bool, error) { + out, _, err := c.Nodes().List(nil) + if err != nil { + return false, err + } + if n := len(out); n != 1 { + return false, fmt.Errorf("expected 1 node, got: %d", n) + } + nodeID = out[0].ID + return true, nil + }, func(err error) { + t.Fatalf("err: %s", err) + }) + + // Perform the node purge and check the response objects. + out, meta, err := c.Nodes().Purge(nodeID, nil) + require.Nil(err) + require.NotNil(out) + + // We can't use assertQueryMeta here, as the RPC response does not populate + // the known leader field. + require.Greater(meta.LastIndex, uint64(0)) +} + func TestNodeStatValueFormatting(t *testing.T) { t.Parallel()