github.com/rohankumardubey/nomad@v0.11.8/command/alloc_exec_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/nomad/helper/uuid"
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/hashicorp/nomad/testutil"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/posener/complete"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  // static check
    20  var _ cli.Command = &AllocExecCommand{}
    21  
    22  func TestAllocExecCommand_Fails(t *testing.T) {
    23  	t.Parallel()
    24  	srv, client, url := testServer(t, true, nil)
    25  	defer srv.Shutdown()
    26  
    27  	cases := []struct {
    28  		name          string
    29  		args          []string
    30  		expectedError string
    31  	}{
    32  		{
    33  			"misuse",
    34  			[]string{"bad"},
    35  			commandErrorText(&AllocExecCommand{}),
    36  		},
    37  		{
    38  			"connection failure",
    39  			[]string{"-address=nope", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
    40  			"Error querying allocation",
    41  		},
    42  		{
    43  			"not found alloc",
    44  			[]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
    45  			"No allocation(s) with prefix or id",
    46  		},
    47  		{
    48  			"not found job",
    49  			[]string{"-address=" + url, "-job", "example", "/bin/bash"},
    50  			`job "example" doesn't exist`,
    51  		},
    52  		{
    53  			"too short allocis",
    54  			[]string{"-address=" + url, "2", "/bin/bash"},
    55  			"Alloc ID must contain at least two characters",
    56  		},
    57  		{
    58  			"missing command",
    59  			[]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C"},
    60  			"A command is required",
    61  		},
    62  		{
    63  			"long escaped char",
    64  			[]string{"-address=" + url, "-e", "long_escape", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
    65  			"a single character",
    66  		},
    67  	}
    68  
    69  	for _, c := range cases {
    70  		t.Run(c.name, func(t *testing.T) {
    71  			ui := new(cli.MockUi)
    72  			cmd := &AllocExecCommand{Meta: Meta{Ui: ui}}
    73  
    74  			code := cmd.Run(c.args)
    75  			require.Equal(t, 1, code)
    76  
    77  			require.Contains(t, ui.ErrorWriter.String(), c.expectedError)
    78  
    79  			ui.ErrorWriter.Reset()
    80  			ui.OutputWriter.Reset()
    81  
    82  		})
    83  	}
    84  
    85  	// Wait for a node to be ready
    86  	testutil.WaitForResult(func() (bool, error) {
    87  		nodes, _, err := client.Nodes().List(nil)
    88  		if err != nil {
    89  			return false, err
    90  		}
    91  		for _, node := range nodes {
    92  			if _, ok := node.Drivers["mock_driver"]; ok &&
    93  				node.Status == structs.NodeStatusReady {
    94  				return true, nil
    95  			}
    96  		}
    97  		return false, fmt.Errorf("no ready nodes")
    98  	}, func(err error) {
    99  		require.NoError(t, err)
   100  	})
   101  
   102  	t.Run("non existent task", func(t *testing.T) {
   103  		ui := new(cli.MockUi)
   104  		cmd := &AllocExecCommand{Meta: Meta{Ui: ui}}
   105  
   106  		jobID := "job1_sfx"
   107  		job1 := testJob(jobID)
   108  		resp, _, err := client.Jobs().Register(job1, nil)
   109  		require.NoError(t, err)
   110  		code := waitForSuccess(ui, client, fullId, t, resp.EvalID)
   111  		require.Zero(t, code, "status code not zero")
   112  
   113  		// get an alloc id
   114  		allocId1 := ""
   115  		if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil {
   116  			if len(allocs) > 0 {
   117  				allocId1 = allocs[0].ID
   118  			}
   119  		}
   120  		require.NotEmpty(t, allocId1, "unable to find allocation")
   121  
   122  		// by alloc
   123  		require.Equal(t, 1, cmd.Run([]string{"-address=" + url, "-task=nonexistenttask1", allocId1, "/bin/bash"}))
   124  		require.Contains(t, ui.ErrorWriter.String(), "Could not find task named: nonexistenttask1")
   125  		ui.ErrorWriter.Reset()
   126  
   127  		// by jobID
   128  		require.Equal(t, 1, cmd.Run([]string{"-address=" + url, "-task=nonexistenttask2", "-job", jobID, "/bin/bash"}))
   129  		require.Contains(t, ui.ErrorWriter.String(), "Could not find task named: nonexistenttask2")
   130  		ui.ErrorWriter.Reset()
   131  	})
   132  
   133  }
   134  
   135  func TestAllocExecCommand_AutocompleteArgs(t *testing.T) {
   136  	assert := assert.New(t)
   137  	t.Parallel()
   138  
   139  	srv, _, url := testServer(t, true, nil)
   140  	defer srv.Shutdown()
   141  
   142  	ui := new(cli.MockUi)
   143  	cmd := &AllocExecCommand{Meta: Meta{Ui: ui, flagAddress: url}}
   144  
   145  	// Create a fake alloc
   146  	state := srv.Agent.Server().State()
   147  	a := mock.Alloc()
   148  	assert.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a}))
   149  
   150  	prefix := a.ID[:5]
   151  	args := complete.Args{Last: prefix}
   152  	predictor := cmd.AutocompleteArgs()
   153  
   154  	res := predictor.Predict(args)
   155  	assert.Equal(1, len(res))
   156  	assert.Equal(a.ID, res[0])
   157  }
   158  
   159  func TestAllocExecCommand_Run(t *testing.T) {
   160  	t.Parallel()
   161  	srv, client, url := testServer(t, true, nil)
   162  	defer srv.Shutdown()
   163  
   164  	// Wait for a node to be ready
   165  	testutil.WaitForResult(func() (bool, error) {
   166  		nodes, _, err := client.Nodes().List(nil)
   167  		if err != nil {
   168  			return false, err
   169  		}
   170  
   171  		for _, node := range nodes {
   172  			if _, ok := node.Drivers["mock_driver"]; ok &&
   173  				node.Status == structs.NodeStatusReady {
   174  				return true, nil
   175  			}
   176  		}
   177  		return false, fmt.Errorf("no ready nodes")
   178  	}, func(err error) {
   179  		require.NoError(t, err)
   180  	})
   181  
   182  	jobID := uuid.Generate()
   183  	job := testJob(jobID)
   184  	job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
   185  		"run_for": "10s",
   186  		"exec_command": map[string]interface{}{
   187  			"run_for":       "1ms",
   188  			"exit_code":     21,
   189  			"stdout_string": "sample stdout output\n",
   190  			"stderr_string": "sample stderr output\n",
   191  		},
   192  	}
   193  	resp, _, err := client.Jobs().Register(job, nil)
   194  	require.NoError(t, err)
   195  
   196  	evalUi := new(cli.MockUi)
   197  	code := waitForSuccess(evalUi, client, fullId, t, resp.EvalID)
   198  	require.Equal(t, 0, code, "failed to get status - output: %v", evalUi.ErrorWriter.String())
   199  
   200  	allocId := ""
   201  
   202  	testutil.WaitForResult(func() (bool, error) {
   203  		allocs, _, err := client.Jobs().Allocations(jobID, false, nil)
   204  		if err != nil {
   205  			return false, fmt.Errorf("failed to get allocations: %v", err)
   206  		}
   207  
   208  		if len(allocs) < 0 {
   209  			return false, fmt.Errorf("no allocations yet")
   210  		}
   211  
   212  		alloc := allocs[0]
   213  		if alloc.ClientStatus != "running" {
   214  			return false, fmt.Errorf("alloc is not running yet: %v", alloc.ClientStatus)
   215  		}
   216  
   217  		allocId = alloc.ID
   218  		return true, nil
   219  	}, func(err error) {
   220  		require.NoError(t, err)
   221  
   222  	})
   223  
   224  	cases := []struct {
   225  		name    string
   226  		command string
   227  		stdin   string
   228  
   229  		stdout   string
   230  		stderr   string
   231  		exitCode int
   232  	}{
   233  		{
   234  			name:     "basic stdout/err",
   235  			command:  "simplecommand",
   236  			stdin:    "",
   237  			stdout:   "sample stdout output",
   238  			stderr:   "sample stderr output",
   239  			exitCode: 21,
   240  		},
   241  		{
   242  			name:     "notty: streamining input",
   243  			command:  "showinput",
   244  			stdin:    "hello from stdin",
   245  			stdout:   "TTY: false\nStdin:\nhello from stdin",
   246  			exitCode: 0,
   247  		},
   248  	}
   249  
   250  	for _, c := range cases {
   251  		t.Run("by id: "+c.name, func(t *testing.T) {
   252  			ui := new(cli.MockUi)
   253  			var stdout, stderr bufferCloser
   254  
   255  			cmd := &AllocExecCommand{
   256  				Meta:   Meta{Ui: ui},
   257  				Stdin:  strings.NewReader(c.stdin),
   258  				Stdout: &stdout,
   259  				Stderr: &stderr,
   260  			}
   261  
   262  			code = cmd.Run([]string{"-address=" + url, allocId, c.command})
   263  			assert.Equal(t, c.exitCode, code)
   264  			assert.Equal(t, c.stdout, strings.TrimSpace(stdout.String()))
   265  			assert.Equal(t, c.stderr, strings.TrimSpace(stderr.String()))
   266  		})
   267  		t.Run("by job: "+c.name, func(t *testing.T) {
   268  			ui := new(cli.MockUi)
   269  			var stdout, stderr bufferCloser
   270  
   271  			cmd := &AllocExecCommand{
   272  				Meta:   Meta{Ui: ui},
   273  				Stdin:  strings.NewReader(c.stdin),
   274  				Stdout: &stdout,
   275  				Stderr: &stderr,
   276  			}
   277  
   278  			code = cmd.Run([]string{"-address=" + url, "-job", jobID, c.command})
   279  			assert.Equal(t, c.exitCode, code)
   280  			assert.Equal(t, c.stdout, strings.TrimSpace(stdout.String()))
   281  			assert.Equal(t, c.stderr, strings.TrimSpace(stderr.String()))
   282  		})
   283  	}
   284  }
   285  
   286  type bufferCloser struct {
   287  	bytes.Buffer
   288  }
   289  
   290  func (b *bufferCloser) Close() error {
   291  	return nil
   292  }