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