github.com/djenriquez/nomad-1@v0.8.1/command/alloc_status_test.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/nomad/helper/uuid" 11 "github.com/hashicorp/nomad/nomad/mock" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/testutil" 14 "github.com/mitchellh/cli" 15 "github.com/posener/complete" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestAllocStatusCommand_Implements(t *testing.T) { 21 t.Parallel() 22 var _ cli.Command = &AllocStatusCommand{} 23 } 24 25 func TestAllocStatusCommand_Fails(t *testing.T) { 26 t.Parallel() 27 srv, _, url := testServer(t, false, nil) 28 defer srv.Shutdown() 29 30 ui := new(cli.MockUi) 31 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}} 32 33 // Fails on misuse 34 if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { 35 t.Fatalf("expected exit code 1, got: %d", code) 36 } 37 if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) { 38 t.Fatalf("expected help output, got: %s", out) 39 } 40 ui.ErrorWriter.Reset() 41 42 // Fails on connection failure 43 if code := cmd.Run([]string{"-address=nope", "foobar"}); code != 1 { 44 t.Fatalf("expected exit code 1, got: %d", code) 45 } 46 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying allocation") { 47 t.Fatalf("expected failed query error, got: %s", out) 48 } 49 ui.ErrorWriter.Reset() 50 51 // Fails on missing alloc 52 if code := cmd.Run([]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C"}); code != 1 { 53 t.Fatalf("expected exit 1, got: %d", code) 54 } 55 if out := ui.ErrorWriter.String(); !strings.Contains(out, "No allocation(s) with prefix or id") { 56 t.Fatalf("expected not found error, got: %s", out) 57 } 58 ui.ErrorWriter.Reset() 59 60 // Fail on identifier with too few characters 61 if code := cmd.Run([]string{"-address=" + url, "2"}); code != 1 { 62 t.Fatalf("expected exit 1, got: %d", code) 63 } 64 if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") { 65 t.Fatalf("expected too few characters error, got: %s", out) 66 } 67 ui.ErrorWriter.Reset() 68 69 // Identifiers with uneven length should produce a query result 70 if code := cmd.Run([]string{"-address=" + url, "123"}); code != 1 { 71 t.Fatalf("expected exit 1, got: %d", code) 72 } 73 if out := ui.ErrorWriter.String(); !strings.Contains(out, "No allocation(s) with prefix or id") { 74 t.Fatalf("expected not found error, got: %s", out) 75 } 76 ui.ErrorWriter.Reset() 77 78 // Failed on both -json and -t options are specified 79 if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 { 80 t.Fatalf("expected exit 1, got: %d", code) 81 } 82 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both json and template formatting are not allowed") { 83 t.Fatalf("expected getting formatter error, got: %s", out) 84 } 85 } 86 87 func TestAllocStatusCommand_Run(t *testing.T) { 88 t.Parallel() 89 srv, client, url := testServer(t, true, nil) 90 defer srv.Shutdown() 91 92 // Wait for a node to be ready 93 testutil.WaitForResult(func() (bool, error) { 94 nodes, _, err := client.Nodes().List(nil) 95 if err != nil { 96 return false, err 97 } 98 for _, node := range nodes { 99 if node.Status == structs.NodeStatusReady { 100 return true, nil 101 } 102 } 103 return false, fmt.Errorf("no ready nodes") 104 }, func(err error) { 105 t.Fatalf("err: %v", err) 106 }) 107 108 ui := new(cli.MockUi) 109 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}} 110 111 jobID := "job1_sfx" 112 job1 := testJob(jobID) 113 resp, _, err := client.Jobs().Register(job1, nil) 114 if err != nil { 115 t.Fatalf("err: %s", err) 116 } 117 if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 { 118 t.Fatalf("status code non zero saw %d", code) 119 } 120 // get an alloc id 121 allocId1 := "" 122 if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil { 123 if len(allocs) > 0 { 124 allocId1 = allocs[0].ID 125 } 126 } 127 if allocId1 == "" { 128 t.Fatal("unable to find an allocation") 129 } 130 131 if code := cmd.Run([]string{"-address=" + url, allocId1}); code != 0 { 132 t.Fatalf("expected exit 0, got: %d", code) 133 } 134 out := ui.OutputWriter.String() 135 if !strings.Contains(out, "Created") { 136 t.Fatalf("expected to have 'Created' but saw: %s", out) 137 } 138 139 if !strings.Contains(out, "Modified") { 140 t.Fatalf("expected to have 'Modified' but saw: %s", out) 141 } 142 143 ui.OutputWriter.Reset() 144 145 if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 { 146 t.Fatalf("expected exit 0, got: %d", code) 147 } 148 out = ui.OutputWriter.String() 149 if !strings.Contains(out, allocId1) { 150 t.Fatal("expected to find alloc id in output") 151 } 152 if !strings.Contains(out, "Created") { 153 t.Fatalf("expected to have 'Created' but saw: %s", out) 154 } 155 ui.OutputWriter.Reset() 156 157 // Try the query with an even prefix that includes the hyphen 158 if code := cmd.Run([]string{"-address=" + url, allocId1[:13]}); code != 0 { 159 t.Fatalf("expected exit 0, got: %d", code) 160 } 161 out = ui.OutputWriter.String() 162 if !strings.Contains(out, "Created") { 163 t.Fatalf("expected to have 'Created' but saw: %s", out) 164 } 165 ui.OutputWriter.Reset() 166 167 if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 { 168 t.Fatalf("expected exit 0, got: %d", code) 169 } 170 out = ui.OutputWriter.String() 171 if !strings.Contains(out, allocId1) { 172 t.Fatal("expected to find alloc id in output") 173 } 174 ui.OutputWriter.Reset() 175 176 } 177 178 func TestAllocStatusCommand_RescheduleInfo(t *testing.T) { 179 t.Parallel() 180 srv, client, url := testServer(t, true, nil) 181 defer srv.Shutdown() 182 183 // Wait for a node to be ready 184 testutil.WaitForResult(func() (bool, error) { 185 nodes, _, err := client.Nodes().List(nil) 186 if err != nil { 187 return false, err 188 } 189 for _, node := range nodes { 190 if node.Status == structs.NodeStatusReady { 191 return true, nil 192 } 193 } 194 return false, fmt.Errorf("no ready nodes") 195 }, func(err error) { 196 t.Fatalf("err: %v", err) 197 }) 198 199 ui := new(cli.MockUi) 200 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}} 201 // Test reschedule attempt info 202 require := require.New(t) 203 state := srv.Agent.Server().State() 204 a := mock.Alloc() 205 a.Metrics = &structs.AllocMetric{} 206 nextAllocId := uuid.Generate() 207 a.NextAllocation = nextAllocId 208 a.RescheduleTracker = &structs.RescheduleTracker{ 209 Events: []*structs.RescheduleEvent{ 210 { 211 RescheduleTime: time.Now().Add(-2 * time.Minute).UTC().UnixNano(), 212 PrevAllocID: uuid.Generate(), 213 PrevNodeID: uuid.Generate(), 214 }, 215 }, 216 } 217 require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) 218 219 if code := cmd.Run([]string{"-address=" + url, a.ID}); code != 0 { 220 t.Fatalf("expected exit 0, got: %d", code) 221 } 222 out := ui.OutputWriter.String() 223 require.Contains(out, "Replacement Alloc ID") 224 require.Regexp(regexp.MustCompile(".*Reschedule Attempts\\s*=\\s*1/2"), out) 225 } 226 227 func TestAllocStatusCommand_AutocompleteArgs(t *testing.T) { 228 assert := assert.New(t) 229 t.Parallel() 230 231 srv, _, url := testServer(t, true, nil) 232 defer srv.Shutdown() 233 234 ui := new(cli.MockUi) 235 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}} 236 237 // Create a fake alloc 238 state := srv.Agent.Server().State() 239 a := mock.Alloc() 240 assert.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) 241 242 prefix := a.ID[:5] 243 args := complete.Args{Last: prefix} 244 predictor := cmd.AutocompleteArgs() 245 246 res := predictor.Predict(args) 247 assert.Equal(1, len(res)) 248 assert.Equal(a.ID, res[0]) 249 }