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  }