github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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  }