github.com/hernad/nomad@v1.6.112/command/node_status_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package command 5 6 import ( 7 "fmt" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/hernad/nomad/api" 13 "github.com/hernad/nomad/ci" 14 "github.com/hernad/nomad/command/agent" 15 "github.com/hernad/nomad/testutil" 16 "github.com/mitchellh/cli" 17 "github.com/posener/complete" 18 "github.com/stretchr/testify/assert" 19 ) 20 21 func TestNodeStatusCommand_Implements(t *testing.T) { 22 ci.Parallel(t) 23 var _ cli.Command = &NodeStatusCommand{} 24 } 25 26 func TestNodeStatusCommand_Self(t *testing.T) { 27 ci.Parallel(t) 28 // Start in dev mode so we get a node registration 29 srv, client, url := testServer(t, true, func(c *agent.Config) { 30 c.NodeName = "mynode" 31 }) 32 defer srv.Shutdown() 33 34 ui := cli.NewMockUi() 35 cmd := &NodeStatusCommand{Meta: Meta{Ui: ui}} 36 37 // Wait for a node to appear 38 var nodeID string 39 testutil.WaitForResult(func() (bool, error) { 40 nodes, _, err := client.Nodes().List(nil) 41 if err != nil { 42 return false, err 43 } 44 if len(nodes) == 0 { 45 return false, fmt.Errorf("missing node") 46 } 47 nodeID = nodes[0].ID 48 return true, nil 49 }, func(err error) { 50 t.Fatalf("err: %s", err) 51 }) 52 53 // Query self node 54 if code := cmd.Run([]string{"-address=" + url, "-self"}); code != 0 { 55 t.Fatalf("expected exit 0, got: %d", code) 56 } 57 out := ui.OutputWriter.String() 58 if !strings.Contains(out, "mynode") { 59 t.Fatalf("expect to find mynode, got: %s", out) 60 } 61 if !strings.Contains(out, "No allocations placed") { 62 t.Fatalf("should not dump allocations") 63 } 64 ui.OutputWriter.Reset() 65 66 // Request full id output 67 if code := cmd.Run([]string{"-address=" + url, "-self", "-verbose"}); code != 0 { 68 t.Fatalf("expected exit 0, got: %d", code) 69 } 70 out = ui.OutputWriter.String() 71 if !strings.Contains(out, nodeID) { 72 t.Fatalf("expected full node id %q, got: %s", nodeID, out) 73 } 74 ui.OutputWriter.Reset() 75 } 76 77 func TestNodeStatusCommand_Run(t *testing.T) { 78 ci.Parallel(t) 79 // Start in dev mode so we get a node registration 80 srv, client, url := testServer(t, true, func(c *agent.Config) { 81 c.NodeName = "mynode" 82 }) 83 defer srv.Shutdown() 84 85 ui := cli.NewMockUi() 86 cmd := &NodeStatusCommand{Meta: Meta{Ui: ui}} 87 88 // Wait for a node to appear 89 var nodeID string 90 testutil.WaitForResult(func() (bool, error) { 91 nodes, _, err := client.Nodes().List(nil) 92 if err != nil { 93 return false, err 94 } 95 if len(nodes) == 0 { 96 return false, fmt.Errorf("missing node") 97 } 98 nodeID = nodes[0].ID 99 return true, nil 100 }, func(err error) { 101 t.Fatalf("err: %s", err) 102 }) 103 104 // Query all node statuses 105 if code := cmd.Run([]string{"-address=" + url}); code != 0 { 106 t.Fatalf("expected exit 0, got: %d", code) 107 } 108 out := ui.OutputWriter.String() 109 if !strings.Contains(out, "mynode") { 110 t.Fatalf("expect to find mynode, got: %s", out) 111 } 112 ui.OutputWriter.Reset() 113 114 // Query a single node 115 if code := cmd.Run([]string{"-address=" + url, nodeID}); code != 0 { 116 t.Fatalf("expected exit 0, got: %d", code) 117 } 118 out = ui.OutputWriter.String() 119 if !strings.Contains(out, "mynode") { 120 t.Fatalf("expect to find mynode, got: %s", out) 121 } 122 ui.OutputWriter.Reset() 123 124 // Query single node in short view 125 if code := cmd.Run([]string{"-address=" + url, "-short", nodeID}); code != 0 { 126 t.Fatalf("expected exit 0, got: %d", code) 127 } 128 out = ui.OutputWriter.String() 129 if !strings.Contains(out, "mynode") { 130 t.Fatalf("expect to find mynode, got: %s", out) 131 } 132 if !strings.Contains(out, "No allocations placed") { 133 t.Fatalf("should not dump allocations") 134 } 135 136 // Query a single node based on a prefix that is even without the hyphen 137 if code := cmd.Run([]string{"-address=" + url, nodeID[:13]}); code != 0 { 138 t.Fatalf("expected exit 0, got: %d", code) 139 } 140 out = ui.OutputWriter.String() 141 if !strings.Contains(out, "mynode") { 142 t.Fatalf("expect to find mynode, got: %s", out) 143 } 144 if !strings.Contains(out, nodeID) { 145 t.Fatalf("expected node id %q, got: %s", nodeID, out) 146 } 147 ui.OutputWriter.Reset() 148 149 // Request full id output 150 if code := cmd.Run([]string{"-address=" + url, "-verbose", nodeID[:4]}); code != 0 { 151 t.Fatalf("expected exit 0, got: %d", code) 152 } 153 out = ui.OutputWriter.String() 154 if !strings.Contains(out, nodeID) { 155 t.Fatalf("expected full node id %q, got: %s", nodeID, out) 156 } 157 ui.OutputWriter.Reset() 158 159 // Identifiers with uneven length should produce a query result 160 if code := cmd.Run([]string{"-address=" + url, nodeID[:3]}); code != 0 { 161 t.Fatalf("expected exit 0, got: %d", code) 162 } 163 out = ui.OutputWriter.String() 164 if !strings.Contains(out, "mynode") { 165 t.Fatalf("expect to find mynode, got: %s", out) 166 } 167 } 168 169 func TestNodeStatusCommand_Fails(t *testing.T) { 170 ci.Parallel(t) 171 srv, _, url := testServer(t, false, nil) 172 defer srv.Shutdown() 173 174 ui := cli.NewMockUi() 175 cmd := &NodeStatusCommand{Meta: Meta{Ui: ui}} 176 177 // Fails on misuse 178 if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { 179 t.Fatalf("expected exit code 1, got: %d", code) 180 } 181 if out := ui.ErrorWriter.String(); !strings.Contains(out, commandErrorText(cmd)) { 182 t.Fatalf("expected help output, got: %s", out) 183 } 184 ui.ErrorWriter.Reset() 185 186 // Fails on connection failure 187 if code := cmd.Run([]string{"-address=nope"}); code != 1 { 188 t.Fatalf("expected exit code 1, got: %d", code) 189 } 190 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying node status") { 191 t.Fatalf("expected failed query error, got: %s", out) 192 } 193 ui.ErrorWriter.Reset() 194 195 // Fails on nonexistent node 196 if code := cmd.Run([]string{"-address=" + url, "12345678-abcd-efab-cdef-123456789abc"}); code != 1 { 197 t.Fatalf("expected exit 1, got: %d", code) 198 } 199 if out := ui.ErrorWriter.String(); !strings.Contains(out, "No node(s) with prefix") { 200 t.Fatalf("expected not found error, got: %s", out) 201 } 202 ui.ErrorWriter.Reset() 203 204 // Fail on identifier with too few characters 205 if code := cmd.Run([]string{"-address=" + url, "1"}); code != 1 { 206 t.Fatalf("expected exit 1, got: %d", code) 207 } 208 if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") { 209 t.Fatalf("expected too few characters error, got: %s", out) 210 } 211 ui.ErrorWriter.Reset() 212 213 // Failed on both -json and -t options are specified 214 if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 { 215 t.Fatalf("expected exit 1, got: %d", code) 216 } 217 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both json and template formatting are not allowed") { 218 t.Fatalf("expected getting formatter error, got: %s", out) 219 } 220 ui.ErrorWriter.Reset() 221 222 // Fail if -quiet is passed with -verbose 223 if code := cmd.Run([]string{"-address=" + url, "-quiet", "-verbose"}); code != 1 { 224 t.Fatalf("expected exit 1, got: %d", code) 225 } 226 227 if out := ui.ErrorWriter.String(); !strings.Contains(out, "-quiet cannot be used with -verbose or -json") { 228 t.Fatalf("expected getting formatter error, got: %s", out) 229 } 230 ui.ErrorWriter.Reset() 231 232 // Fail if -quiet is passed with -json 233 if code := cmd.Run([]string{"-address=" + url, "-quiet", "-json"}); code != 1 { 234 t.Fatalf("expected exit 1, got: %d", code) 235 } 236 237 if out := ui.ErrorWriter.String(); !strings.Contains(out, "-quiet cannot be used with -verbose or -json") { 238 t.Fatalf("expected getting formatter error, got: %s", out) 239 } 240 } 241 242 func TestNodeStatusCommand_AutocompleteArgs(t *testing.T) { 243 ci.Parallel(t) 244 assert := assert.New(t) 245 246 srv, client, url := testServer(t, true, nil) 247 defer srv.Shutdown() 248 249 // Wait for a node to appear 250 var nodeID string 251 testutil.WaitForResult(func() (bool, error) { 252 nodes, _, err := client.Nodes().List(nil) 253 if err != nil { 254 return false, err 255 } 256 if len(nodes) == 0 { 257 return false, fmt.Errorf("missing node") 258 } 259 nodeID = nodes[0].ID 260 return true, nil 261 }, func(err error) { 262 t.Fatalf("err: %s", err) 263 }) 264 265 ui := cli.NewMockUi() 266 cmd := &NodeStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}} 267 268 prefix := nodeID[:len(nodeID)-5] 269 args := complete.Args{Last: prefix} 270 predictor := cmd.AutocompleteArgs() 271 272 res := predictor.Predict(args) 273 assert.Equal(1, len(res)) 274 assert.Equal(nodeID, res[0]) 275 } 276 277 func TestNodeStatusCommand_FormatDrain(t *testing.T) { 278 ci.Parallel(t) 279 assert := assert.New(t) 280 281 node := &api.Node{} 282 283 assert.Equal("false", formatDrain(node)) 284 285 node.DrainStrategy = &api.DrainStrategy{} 286 assert.Equal("true; no deadline", formatDrain(node)) 287 288 node.DrainStrategy = &api.DrainStrategy{} 289 node.DrainStrategy.Deadline = -1 * time.Second 290 assert.Equal("true; force drain", formatDrain(node)) 291 292 // formatTime special cases Unix(0, 0), so increment by 1 293 node.DrainStrategy = &api.DrainStrategy{} 294 node.DrainStrategy.ForceDeadline = time.Unix(1, 0).UTC() 295 assert.Equal("true; 1970-01-01T00:00:01Z deadline", formatDrain(node)) 296 297 node.DrainStrategy.IgnoreSystemJobs = true 298 assert.Equal("true; 1970-01-01T00:00:01Z deadline; ignoring system jobs", formatDrain(node)) 299 }