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 }