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