github.com/smithx10/nomad@v0.9.1-rc1/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, commandErrorText(cmd)) { 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 _, ok := node.Drivers["mock_driver"]; ok && 100 node.Status == structs.NodeStatusReady { 101 return true, nil 102 } 103 } 104 return false, fmt.Errorf("no ready nodes") 105 }, func(err error) { 106 t.Fatalf("err: %v", err) 107 }) 108 109 ui := new(cli.MockUi) 110 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}} 111 112 jobID := "job1_sfx" 113 job1 := testJob(jobID) 114 resp, _, err := client.Jobs().Register(job1, nil) 115 if err != nil { 116 t.Fatalf("err: %s", err) 117 } 118 if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 { 119 t.Fatalf("status code non zero saw %d", code) 120 } 121 // get an alloc id 122 allocId1 := "" 123 if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil { 124 if len(allocs) > 0 { 125 allocId1 = allocs[0].ID 126 } 127 } 128 if allocId1 == "" { 129 t.Fatal("unable to find an allocation") 130 } 131 132 if code := cmd.Run([]string{"-address=" + url, allocId1}); code != 0 { 133 t.Fatalf("expected exit 0, got: %d", code) 134 } 135 out := ui.OutputWriter.String() 136 if !strings.Contains(out, "Created") { 137 t.Fatalf("expected to have 'Created' but saw: %s", out) 138 } 139 140 if !strings.Contains(out, "Modified") { 141 t.Fatalf("expected to have 'Modified' but saw: %s", out) 142 } 143 144 ui.OutputWriter.Reset() 145 146 if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 { 147 t.Fatalf("expected exit 0, got: %d", code) 148 } 149 out = ui.OutputWriter.String() 150 if !strings.Contains(out, allocId1) { 151 t.Fatal("expected to find alloc id in output") 152 } 153 if !strings.Contains(out, "Created") { 154 t.Fatalf("expected to have 'Created' but saw: %s", out) 155 } 156 ui.OutputWriter.Reset() 157 158 // Try the query with an even prefix that includes the hyphen 159 if code := cmd.Run([]string{"-address=" + url, allocId1[:13]}); code != 0 { 160 t.Fatalf("expected exit 0, got: %d", code) 161 } 162 out = ui.OutputWriter.String() 163 if !strings.Contains(out, "Created") { 164 t.Fatalf("expected to have 'Created' but saw: %s", out) 165 } 166 ui.OutputWriter.Reset() 167 168 if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 { 169 t.Fatalf("expected exit 0, got: %d", code) 170 } 171 out = ui.OutputWriter.String() 172 if !strings.Contains(out, allocId1) { 173 t.Fatal("expected to find alloc id in output") 174 } 175 ui.OutputWriter.Reset() 176 177 } 178 179 func TestAllocStatusCommand_RescheduleInfo(t *testing.T) { 180 t.Parallel() 181 srv, client, url := testServer(t, true, nil) 182 defer srv.Shutdown() 183 184 // Wait for a node to be ready 185 testutil.WaitForResult(func() (bool, error) { 186 nodes, _, err := client.Nodes().List(nil) 187 if err != nil { 188 return false, err 189 } 190 for _, node := range nodes { 191 if node.Status == structs.NodeStatusReady { 192 return true, nil 193 } 194 } 195 return false, fmt.Errorf("no ready nodes") 196 }, func(err error) { 197 t.Fatalf("err: %v", err) 198 }) 199 200 ui := new(cli.MockUi) 201 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}} 202 // Test reschedule attempt info 203 require := require.New(t) 204 state := srv.Agent.Server().State() 205 a := mock.Alloc() 206 a.Metrics = &structs.AllocMetric{} 207 nextAllocId := uuid.Generate() 208 a.NextAllocation = nextAllocId 209 a.RescheduleTracker = &structs.RescheduleTracker{ 210 Events: []*structs.RescheduleEvent{ 211 { 212 RescheduleTime: time.Now().Add(-2 * time.Minute).UTC().UnixNano(), 213 PrevAllocID: uuid.Generate(), 214 PrevNodeID: uuid.Generate(), 215 }, 216 }, 217 } 218 require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) 219 220 if code := cmd.Run([]string{"-address=" + url, a.ID}); code != 0 { 221 t.Fatalf("expected exit 0, got: %d", code) 222 } 223 out := ui.OutputWriter.String() 224 require.Contains(out, "Replacement Alloc ID") 225 require.Regexp(regexp.MustCompile(".*Reschedule Attempts\\s*=\\s*1/2"), out) 226 } 227 228 func TestAllocStatusCommand_ScoreMetrics(t *testing.T) { 229 t.Parallel() 230 srv, client, url := testServer(t, true, nil) 231 defer srv.Shutdown() 232 233 // Wait for a node to be ready 234 testutil.WaitForResult(func() (bool, error) { 235 nodes, _, err := client.Nodes().List(nil) 236 if err != nil { 237 return false, err 238 } 239 for _, node := range nodes { 240 if node.Status == structs.NodeStatusReady { 241 return true, nil 242 } 243 } 244 return false, fmt.Errorf("no ready nodes") 245 }, func(err error) { 246 t.Fatalf("err: %v", err) 247 }) 248 249 ui := new(cli.MockUi) 250 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}} 251 // Test node metrics 252 require := require.New(t) 253 state := srv.Agent.Server().State() 254 a := mock.Alloc() 255 mockNode1 := mock.Node() 256 mockNode2 := mock.Node() 257 a.Metrics = &structs.AllocMetric{ 258 ScoreMetaData: []*structs.NodeScoreMeta{ 259 { 260 NodeID: mockNode1.ID, 261 Scores: map[string]float64{ 262 "binpack": 0.77, 263 "node-affinity": 0.5, 264 }, 265 }, 266 { 267 NodeID: mockNode2.ID, 268 Scores: map[string]float64{ 269 "binpack": 0.75, 270 "node-affinity": 0.33, 271 }, 272 }, 273 }, 274 } 275 require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) 276 277 if code := cmd.Run([]string{"-address=" + url, "-verbose", a.ID}); code != 0 { 278 t.Fatalf("expected exit 0, got: %d", code) 279 } 280 out := ui.OutputWriter.String() 281 require.Contains(out, "Placement Metrics") 282 require.Contains(out, mockNode1.ID) 283 require.Contains(out, mockNode2.ID) 284 require.Contains(out, "final score") 285 } 286 287 func TestAllocStatusCommand_AutocompleteArgs(t *testing.T) { 288 assert := assert.New(t) 289 t.Parallel() 290 291 srv, _, url := testServer(t, true, nil) 292 defer srv.Shutdown() 293 294 ui := new(cli.MockUi) 295 cmd := &AllocStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}} 296 297 // Create a fake alloc 298 state := srv.Agent.Server().State() 299 a := mock.Alloc() 300 assert.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) 301 302 prefix := a.ID[:5] 303 args := complete.Args{Last: prefix} 304 predictor := cmd.AutocompleteArgs() 305 306 res := predictor.Predict(args) 307 assert.Equal(1, len(res)) 308 assert.Equal(a.ID, res[0]) 309 }