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