github.com/bigcommerce/nomad@v0.9.3-bc/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 }