From 7f102bcea88c4e63319189dc7587bdbe156ae0e9 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Thu, 25 Apr 2019 13:37:26 +0200 Subject: [PATCH] alloc_signal: Add autcompletion and cmd tests --- client/alloc_endpoint.go | 3 +- client/alloc_endpoint_test.go | 8 --- command/alloc_signal.go | 45 +++++++++++++++-- command/alloc_signal_test.go | 92 +++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 14 deletions(-) diff --git a/client/alloc_endpoint.go b/client/alloc_endpoint.go index c22179606ce..c141f0e36cf 100644 --- a/client/alloc_endpoint.go +++ b/client/alloc_endpoint.go @@ -48,10 +48,11 @@ func (a *Allocations) GarbageCollect(args *nstructs.AllocSpecificRequest, reply return nil } +// Signal is used to send a signal to an allocation's tasks on a client. func (a *Allocations) Signal(args *nstructs.AllocSignalRequest, reply *nstructs.GenericResponse) error { defer metrics.MeasureSince([]string{"client", "allocations", "signal"}, time.Now()) - // Check submit job permissions + // Check alloc-lifecycle permissions if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) { diff --git a/client/alloc_endpoint_test.go b/client/alloc_endpoint_test.go index b1bf686ba8d..a29d60eeecc 100644 --- a/client/alloc_endpoint_test.go +++ b/client/alloc_endpoint_test.go @@ -356,14 +356,6 @@ func TestAllocations_Signal_ACL(t *testing.T) { } } -func TestAllocations_Signal_Subtask(t *testing.T) { - t.Parallel() -} - -func TestAllocations_Signal_EntireAlloc(t *testing.T) { - t.Parallel() -} - func TestAllocations_Stats(t *testing.T) { t.Parallel() require := require.New(t) diff --git a/command/alloc_signal.go b/command/alloc_signal.go index 0aa40bb3d3d..bc33825a0df 100644 --- a/command/alloc_signal.go +++ b/command/alloc_signal.go @@ -3,6 +3,9 @@ package command import ( "fmt" "strings" + + "github.com/hashicorp/nomad/api/contexts" + "github.com/posener/complete" ) type AllocSignalCommand struct { @@ -71,11 +74,6 @@ func (c *AllocSignalCommand) Run(args []string) int { allocID = sanitizeUUIDPrefix(allocID) - var taskName string - if len(args) == 2 { - taskName = args[1] - } - // Get the HTTP client client, err := c.Meta.Client() if err != nil { @@ -108,6 +106,17 @@ func (c *AllocSignalCommand) Run(args []string) int { return 1 } + var taskName string + if len(args) == 2 { + // Validate Task + taskName = args[1] + err := validateTaskExistsInAllocation(taskName, alloc) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + } + err = client.Allocations().Signal(alloc, nil, taskName, signal) if err != nil { c.Ui.Error(fmt.Sprintf("Error signalling allocation: %s", err)) @@ -120,3 +129,29 @@ func (c *AllocSignalCommand) Run(args []string) int { func (a *AllocSignalCommand) Synopsis() string { return "Signal a running allocation" } + +func (c *AllocSignalCommand) AutocompleteFlags() complete.Flags { + return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), + complete.Flags{ + "-s": complete.PredictNothing, + "-verbose": complete.PredictNothing, + }) +} +func (c *AllocSignalCommand) AutocompleteArgs() complete.Predictor { + // Here we only autocomplete allocation names. Eventually we may consider + // expanding this to also autocomplete task names. To do so, we'll need to + // either change the autocompletion api, or implement parsing such that we can + // easily compute the current arg position. + return complete.PredictFunc(func(a complete.Args) []string { + client, err := c.Meta.Client() + if err != nil { + return nil + } + + resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Allocs, nil) + if err != nil { + return []string{} + } + return resp.Matches[contexts.Allocs] + }) +} diff --git a/command/alloc_signal_test.go b/command/alloc_signal_test.go index 02fea72419d..87cdb46fbed 100644 --- a/command/alloc_signal_test.go +++ b/command/alloc_signal_test.go @@ -1,9 +1,16 @@ package command import ( + "fmt" "testing" + "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/nomad/mock" + "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" + "github.com/posener/complete" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -48,3 +55,88 @@ func TestAllocSignalCommand_Fails(t *testing.T) { require.Contains(ui.ErrorWriter.String(), "must contain at least two characters.") ui.ErrorWriter.Reset() } + +func TestAllocSignalCommand_AutocompleteArgs(t *testing.T) { + assert := assert.New(t) + + srv, _, url := testServer(t, true, nil) + defer srv.Shutdown() + + ui := new(cli.MockUi) + cmd := &AllocSignalCommand{Meta: Meta{Ui: ui, flagAddress: url}} + + // Create a fake alloc + state := srv.Agent.Server().State() + a := mock.Alloc() + assert.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) + + prefix := a.ID[:5] + args := complete.Args{All: []string{"signal", prefix}, Last: prefix} + predictor := cmd.AutocompleteArgs() + + // Match Allocs + res := predictor.Predict(args) + assert.Equal(1, len(res)) + assert.Equal(a.ID, res[0]) +} + +func TestAllocSignalCommand_Run(t *testing.T) { + srv, client, url := testServer(t, true, nil) + defer srv.Shutdown() + + require := require.New(t) + + // Wait for a node to be ready + testutil.WaitForResult(func() (bool, error) { + nodes, _, err := client.Nodes().List(nil) + if err != nil { + return false, err + } + for _, node := range nodes { + if _, ok := node.Drivers["mock_driver"]; ok && + node.Status == structs.NodeStatusReady { + return true, nil + } + } + return false, fmt.Errorf("no ready nodes") + }, func(err error) { + t.Fatalf("err: %v", err) + }) + + ui := new(cli.MockUi) + cmd := &AllocSignalCommand{Meta: Meta{Ui: ui}} + + jobID := "job1_sfx" + job1 := testJob(jobID) + resp, _, err := client.Jobs().Register(job1, nil) + require.NoError(err) + if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 { + t.Fatalf("status code non zero saw %d", code) + } + // get an alloc id + allocId1 := "" + if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil { + if len(allocs) > 0 { + allocId1 = allocs[0].ID + } + } + require.NotEmpty(allocId1, "unable to find allocation") + + // Wait for alloc to be running + testutil.WaitForResult(func() (bool, error) { + alloc, _, err := client.Allocations().Info(allocId1, nil) + if err != nil { + return false, err + } + if alloc.ClientStatus == api.AllocClientStatusRunning { + return true, nil + } + return false, fmt.Errorf("alloc is not running, is: %s", alloc.ClientStatus) + }, func(err error) { + t.Fatalf("err: %v", err) + }) + + require.Equal(cmd.Run([]string{"-address=" + url, allocId1}), 0, "expected successful exit code") + + ui.OutputWriter.Reset() +}