github.com/adityamillind98/nomad@v0.11.8/command/alloc_status_test.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"regexp"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/command/agent"
    13  	"github.com/hashicorp/nomad/helper/uuid"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/hashicorp/nomad/testutil"
    17  	"github.com/mitchellh/cli"
    18  	"github.com/posener/complete"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func TestAllocStatusCommand_Implements(t *testing.T) {
    24  	t.Parallel()
    25  	var _ cli.Command = &AllocStatusCommand{}
    26  }
    27  
    28  func TestAllocStatusCommand_Fails(t *testing.T) {
    29  	t.Parallel()
    30  	srv, _, url := testServer(t, false, nil)
    31  	defer srv.Shutdown()
    32  
    33  	ui := new(cli.MockUi)
    34  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
    35  
    36  	// Fails on misuse
    37  	if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 {
    38  		t.Fatalf("expected exit code 1, got: %d", code)
    39  	}
    40  	if out := ui.ErrorWriter.String(); !strings.Contains(out, commandErrorText(cmd)) {
    41  		t.Fatalf("expected help output, got: %s", out)
    42  	}
    43  	ui.ErrorWriter.Reset()
    44  
    45  	// Fails on connection failure
    46  	if code := cmd.Run([]string{"-address=nope", "foobar"}); code != 1 {
    47  		t.Fatalf("expected exit code 1, got: %d", code)
    48  	}
    49  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying allocation") {
    50  		t.Fatalf("expected failed query error, got: %s", out)
    51  	}
    52  	ui.ErrorWriter.Reset()
    53  
    54  	// Fails on missing alloc
    55  	if code := cmd.Run([]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C"}); code != 1 {
    56  		t.Fatalf("expected exit 1, got: %d", code)
    57  	}
    58  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "No allocation(s) with prefix or id") {
    59  		t.Fatalf("expected not found error, got: %s", out)
    60  	}
    61  	ui.ErrorWriter.Reset()
    62  
    63  	// Fail on identifier with too few characters
    64  	if code := cmd.Run([]string{"-address=" + url, "2"}); code != 1 {
    65  		t.Fatalf("expected exit 1, got: %d", code)
    66  	}
    67  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") {
    68  		t.Fatalf("expected too few characters error, got: %s", out)
    69  	}
    70  	ui.ErrorWriter.Reset()
    71  
    72  	// Identifiers with uneven length should produce a query result
    73  	if code := cmd.Run([]string{"-address=" + url, "123"}); code != 1 {
    74  		t.Fatalf("expected exit 1, got: %d", code)
    75  	}
    76  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "No allocation(s) with prefix or id") {
    77  		t.Fatalf("expected not found error, got: %s", out)
    78  	}
    79  	ui.ErrorWriter.Reset()
    80  
    81  	// Failed on both -json and -t options are specified
    82  	if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 {
    83  		t.Fatalf("expected exit 1, got: %d", code)
    84  	}
    85  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both json and template formatting are not allowed") {
    86  		t.Fatalf("expected getting formatter error, got: %s", out)
    87  	}
    88  }
    89  
    90  func TestAllocStatusCommand_LifecycleInfo(t *testing.T) {
    91  	t.Parallel()
    92  	srv, client, url := testServer(t, true, nil)
    93  	defer srv.Shutdown()
    94  
    95  	// Wait for a node to be ready
    96  	testutil.WaitForResult(func() (bool, error) {
    97  		nodes, _, err := client.Nodes().List(nil)
    98  		if err != nil {
    99  			return false, err
   100  		}
   101  		for _, node := range nodes {
   102  			if node.Status == structs.NodeStatusReady {
   103  				return true, nil
   104  			}
   105  		}
   106  		return false, fmt.Errorf("no ready nodes")
   107  	}, func(err error) {
   108  		require.NoError(t, err)
   109  	})
   110  
   111  	ui := new(cli.MockUi)
   112  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
   113  	state := srv.Agent.Server().State()
   114  
   115  	a := mock.Alloc()
   116  	a.Metrics = &structs.AllocMetric{}
   117  	tg := a.Job.LookupTaskGroup(a.TaskGroup)
   118  
   119  	initTask := tg.Tasks[0].Copy()
   120  	initTask.Name = "init_task"
   121  	initTask.Lifecycle = &structs.TaskLifecycleConfig{
   122  		Hook: "prestart",
   123  	}
   124  
   125  	prestartSidecarTask := tg.Tasks[0].Copy()
   126  	prestartSidecarTask.Name = "prestart_sidecar"
   127  	prestartSidecarTask.Lifecycle = &structs.TaskLifecycleConfig{
   128  		Hook:    "prestart",
   129  		Sidecar: true,
   130  	}
   131  
   132  	tg.Tasks = append(tg.Tasks, initTask, prestartSidecarTask)
   133  	a.TaskResources["init_task"] = a.TaskResources["web"]
   134  	a.TaskResources["prestart_sidecar"] = a.TaskResources["web"]
   135  	a.TaskStates = map[string]*structs.TaskState{
   136  		"web":              &structs.TaskState{State: "pending"},
   137  		"init_task":        &structs.TaskState{State: "running"},
   138  		"prestart_sidecar": &structs.TaskState{State: "running"},
   139  	}
   140  
   141  	require.Nil(t, state.UpsertAllocs(1000, []*structs.Allocation{a}))
   142  
   143  	if code := cmd.Run([]string{"-address=" + url, a.ID}); code != 0 {
   144  		t.Fatalf("expected exit 0, got: %d", code)
   145  	}
   146  	out := ui.OutputWriter.String()
   147  
   148  	require.Contains(t, out, `Task "init_task" (prestart) is "running"`)
   149  	require.Contains(t, out, `Task "prestart_sidecar" (prestart sidecar) is "running"`)
   150  	require.Contains(t, out, `Task "web" is "pending"`)
   151  }
   152  
   153  func TestAllocStatusCommand_Run(t *testing.T) {
   154  	t.Parallel()
   155  	srv, client, url := testServer(t, true, nil)
   156  	defer srv.Shutdown()
   157  
   158  	// Wait for a node to be ready
   159  	testutil.WaitForResult(func() (bool, error) {
   160  		nodes, _, err := client.Nodes().List(nil)
   161  		if err != nil {
   162  			return false, err
   163  		}
   164  		for _, node := range nodes {
   165  			if _, ok := node.Drivers["mock_driver"]; ok &&
   166  				node.Status == structs.NodeStatusReady {
   167  				return true, nil
   168  			}
   169  		}
   170  		return false, fmt.Errorf("no ready nodes")
   171  	}, func(err error) {
   172  		t.Fatalf("err: %v", err)
   173  	})
   174  
   175  	ui := new(cli.MockUi)
   176  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
   177  
   178  	jobID := "job1_sfx"
   179  	job1 := testJob(jobID)
   180  	resp, _, err := client.Jobs().Register(job1, nil)
   181  	if err != nil {
   182  		t.Fatalf("err: %s", err)
   183  	}
   184  	if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
   185  		t.Fatalf("status code non zero saw %d", code)
   186  	}
   187  	// get an alloc id
   188  	allocId1 := ""
   189  	nodeName := ""
   190  	if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil {
   191  		if len(allocs) > 0 {
   192  			allocId1 = allocs[0].ID
   193  			nodeName = allocs[0].NodeName
   194  		}
   195  	}
   196  	if allocId1 == "" {
   197  		t.Fatal("unable to find an allocation")
   198  	}
   199  
   200  	if code := cmd.Run([]string{"-address=" + url, allocId1}); code != 0 {
   201  		t.Fatalf("expected exit 0, got: %d", code)
   202  	}
   203  	out := ui.OutputWriter.String()
   204  	if !strings.Contains(out, "Created") {
   205  		t.Fatalf("expected to have 'Created' but saw: %s", out)
   206  	}
   207  
   208  	if !strings.Contains(out, "Modified") {
   209  		t.Fatalf("expected to have 'Modified' but saw: %s", out)
   210  	}
   211  
   212  	nodeNameRegexpStr := fmt.Sprintf(`\nNode Name\s+= %s\n`, regexp.QuoteMeta(nodeName))
   213  	require.Regexp(t, regexp.MustCompile(nodeNameRegexpStr), out)
   214  
   215  	ui.OutputWriter.Reset()
   216  
   217  	if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 {
   218  		t.Fatalf("expected exit 0, got: %d", code)
   219  	}
   220  	out = ui.OutputWriter.String()
   221  	if !strings.Contains(out, allocId1) {
   222  		t.Fatal("expected to find alloc id in output")
   223  	}
   224  	if !strings.Contains(out, "Created") {
   225  		t.Fatalf("expected to have 'Created' but saw: %s", out)
   226  	}
   227  	ui.OutputWriter.Reset()
   228  
   229  	// Try the query with an even prefix that includes the hyphen
   230  	if code := cmd.Run([]string{"-address=" + url, allocId1[:13]}); code != 0 {
   231  		t.Fatalf("expected exit 0, got: %d", code)
   232  	}
   233  	out = ui.OutputWriter.String()
   234  	if !strings.Contains(out, "Created") {
   235  		t.Fatalf("expected to have 'Created' but saw: %s", out)
   236  	}
   237  	ui.OutputWriter.Reset()
   238  
   239  	if code := cmd.Run([]string{"-address=" + url, "-verbose", allocId1}); code != 0 {
   240  		t.Fatalf("expected exit 0, got: %d", code)
   241  	}
   242  	out = ui.OutputWriter.String()
   243  	if !strings.Contains(out, allocId1) {
   244  		t.Fatal("expected to find alloc id in output")
   245  	}
   246  	ui.OutputWriter.Reset()
   247  
   248  }
   249  
   250  func TestAllocStatusCommand_RescheduleInfo(t *testing.T) {
   251  	t.Parallel()
   252  	srv, client, url := testServer(t, true, nil)
   253  	defer srv.Shutdown()
   254  
   255  	// Wait for a node to be ready
   256  	testutil.WaitForResult(func() (bool, error) {
   257  		nodes, _, err := client.Nodes().List(nil)
   258  		if err != nil {
   259  			return false, err
   260  		}
   261  		for _, node := range nodes {
   262  			if node.Status == structs.NodeStatusReady {
   263  				return true, nil
   264  			}
   265  		}
   266  		return false, fmt.Errorf("no ready nodes")
   267  	}, func(err error) {
   268  		t.Fatalf("err: %v", err)
   269  	})
   270  
   271  	ui := new(cli.MockUi)
   272  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
   273  	// Test reschedule attempt info
   274  	require := require.New(t)
   275  	state := srv.Agent.Server().State()
   276  	a := mock.Alloc()
   277  	a.Metrics = &structs.AllocMetric{}
   278  	nextAllocId := uuid.Generate()
   279  	a.NextAllocation = nextAllocId
   280  	a.RescheduleTracker = &structs.RescheduleTracker{
   281  		Events: []*structs.RescheduleEvent{
   282  			{
   283  				RescheduleTime: time.Now().Add(-2 * time.Minute).UTC().UnixNano(),
   284  				PrevAllocID:    uuid.Generate(),
   285  				PrevNodeID:     uuid.Generate(),
   286  			},
   287  		},
   288  	}
   289  	require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a}))
   290  
   291  	if code := cmd.Run([]string{"-address=" + url, a.ID}); code != 0 {
   292  		t.Fatalf("expected exit 0, got: %d", code)
   293  	}
   294  	out := ui.OutputWriter.String()
   295  	require.Contains(out, "Replacement Alloc ID")
   296  	require.Regexp(regexp.MustCompile(".*Reschedule Attempts\\s*=\\s*1/2"), out)
   297  }
   298  
   299  func TestAllocStatusCommand_ScoreMetrics(t *testing.T) {
   300  	t.Parallel()
   301  	srv, client, url := testServer(t, true, nil)
   302  	defer srv.Shutdown()
   303  
   304  	// Wait for a node to be ready
   305  	testutil.WaitForResult(func() (bool, error) {
   306  		nodes, _, err := client.Nodes().List(nil)
   307  		if err != nil {
   308  			return false, err
   309  		}
   310  		for _, node := range nodes {
   311  			if node.Status == structs.NodeStatusReady {
   312  				return true, nil
   313  			}
   314  		}
   315  		return false, fmt.Errorf("no ready nodes")
   316  	}, func(err error) {
   317  		t.Fatalf("err: %v", err)
   318  	})
   319  
   320  	ui := new(cli.MockUi)
   321  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
   322  	// Test node metrics
   323  	require := require.New(t)
   324  	state := srv.Agent.Server().State()
   325  	a := mock.Alloc()
   326  	mockNode1 := mock.Node()
   327  	mockNode2 := mock.Node()
   328  	a.Metrics = &structs.AllocMetric{
   329  		ScoreMetaData: []*structs.NodeScoreMeta{
   330  			{
   331  				NodeID: mockNode1.ID,
   332  				Scores: map[string]float64{
   333  					"binpack":       0.77,
   334  					"node-affinity": 0.5,
   335  				},
   336  			},
   337  			{
   338  				NodeID: mockNode2.ID,
   339  				Scores: map[string]float64{
   340  					"binpack":       0.75,
   341  					"node-affinity": 0.33,
   342  				},
   343  			},
   344  		},
   345  	}
   346  	require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a}))
   347  
   348  	if code := cmd.Run([]string{"-address=" + url, "-verbose", a.ID}); code != 0 {
   349  		t.Fatalf("expected exit 0, got: %d", code)
   350  	}
   351  	out := ui.OutputWriter.String()
   352  	require.Contains(out, "Placement Metrics")
   353  	require.Contains(out, mockNode1.ID)
   354  	require.Contains(out, mockNode2.ID)
   355  
   356  	// assert we sort headers alphabetically
   357  	require.Contains(out, "binpack  node-affinity")
   358  	require.Contains(out, "final score")
   359  }
   360  
   361  func TestAllocStatusCommand_AutocompleteArgs(t *testing.T) {
   362  	assert := assert.New(t)
   363  	t.Parallel()
   364  
   365  	srv, _, url := testServer(t, true, nil)
   366  	defer srv.Shutdown()
   367  
   368  	ui := new(cli.MockUi)
   369  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
   370  
   371  	// Create a fake alloc
   372  	state := srv.Agent.Server().State()
   373  	a := mock.Alloc()
   374  	assert.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a}))
   375  
   376  	prefix := a.ID[:5]
   377  	args := complete.Args{Last: prefix}
   378  	predictor := cmd.AutocompleteArgs()
   379  
   380  	res := predictor.Predict(args)
   381  	assert.Equal(1, len(res))
   382  	assert.Equal(a.ID, res[0])
   383  }
   384  
   385  func TestAllocStatusCommand_HostVolumes(t *testing.T) {
   386  	t.Parallel()
   387  	// We have to create a tempdir for the host volume even though we're
   388  	// not going to use it b/c the server validates the config on startup
   389  	tmpDir, err := ioutil.TempDir("", "vol0")
   390  	if err != nil {
   391  		t.Fatalf("unable to create tempdir for test: %v", err)
   392  	}
   393  	defer os.RemoveAll(tmpDir)
   394  
   395  	vol0 := uuid.Generate()
   396  	srv, _, url := testServer(t, true, func(c *agent.Config) {
   397  		c.Client.HostVolumes = []*structs.ClientHostVolumeConfig{
   398  			{
   399  				Name:     vol0,
   400  				Path:     tmpDir,
   401  				ReadOnly: false,
   402  			},
   403  		}
   404  	})
   405  	defer srv.Shutdown()
   406  	state := srv.Agent.Server().State()
   407  
   408  	// Upsert the job and alloc
   409  	node := mock.Node()
   410  	alloc := mock.Alloc()
   411  	alloc.Metrics = &structs.AllocMetric{}
   412  	alloc.NodeID = node.ID
   413  	job := alloc.Job
   414  	job.TaskGroups[0].Volumes = map[string]*structs.VolumeRequest{
   415  		vol0: {
   416  			Name:   vol0,
   417  			Type:   structs.VolumeTypeHost,
   418  			Source: tmpDir,
   419  		},
   420  	}
   421  	job.TaskGroups[0].Tasks[0].VolumeMounts = []*structs.VolumeMount{
   422  		{
   423  			Volume:          vol0,
   424  			Destination:     "/var/www",
   425  			ReadOnly:        true,
   426  			PropagationMode: "private",
   427  		},
   428  	}
   429  	// fakes the placement enough so that we have something to iterate
   430  	// on in 'nomad alloc status'
   431  	alloc.TaskStates = map[string]*structs.TaskState{
   432  		"web": &structs.TaskState{
   433  			Events: []*structs.TaskEvent{
   434  				structs.NewTaskEvent("test event").SetMessage("test msg"),
   435  			},
   436  		},
   437  	}
   438  	summary := mock.JobSummary(alloc.JobID)
   439  	require.NoError(t, state.UpsertJobSummary(1004, summary))
   440  	require.NoError(t, state.UpsertAllocs(1005, []*structs.Allocation{alloc}))
   441  
   442  	ui := new(cli.MockUi)
   443  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
   444  	if code := cmd.Run([]string{"-address=" + url, "-verbose", alloc.ID}); code != 0 {
   445  		t.Fatalf("expected exit 0, got: %d", code)
   446  	}
   447  	out := ui.OutputWriter.String()
   448  	require.Contains(t, out, "Host Volumes")
   449  	require.Contains(t, out, fmt.Sprintf("%s  true", vol0))
   450  	require.NotContains(t, out, "CSI Volumes")
   451  }
   452  
   453  func TestAllocStatusCommand_CSIVolumes(t *testing.T) {
   454  	t.Parallel()
   455  	srv, _, url := testServer(t, true, nil)
   456  	defer srv.Shutdown()
   457  	state := srv.Agent.Server().State()
   458  
   459  	// Upsert the node, plugin, and volume
   460  	vol0 := uuid.Generate()
   461  	node := mock.Node()
   462  	node.CSINodePlugins = map[string]*structs.CSIInfo{
   463  		"minnie": {
   464  			PluginID: "minnie",
   465  			Healthy:  true,
   466  			NodeInfo: &structs.CSINodeInfo{},
   467  		},
   468  	}
   469  	err := state.UpsertNode(1001, node)
   470  	require.NoError(t, err)
   471  
   472  	vols := []*structs.CSIVolume{{
   473  		ID:             vol0,
   474  		Namespace:      structs.DefaultNamespace,
   475  		PluginID:       "minnie",
   476  		AccessMode:     structs.CSIVolumeAccessModeMultiNodeSingleWriter,
   477  		AttachmentMode: structs.CSIVolumeAttachmentModeFilesystem,
   478  		Topologies: []*structs.CSITopology{{
   479  			Segments: map[string]string{"foo": "bar"},
   480  		}},
   481  	}}
   482  	err = state.CSIVolumeRegister(1002, vols)
   483  	require.NoError(t, err)
   484  
   485  	// Upsert the job and alloc
   486  	alloc := mock.Alloc()
   487  	alloc.Metrics = &structs.AllocMetric{}
   488  	alloc.NodeID = node.ID
   489  	job := alloc.Job
   490  	job.TaskGroups[0].Volumes = map[string]*structs.VolumeRequest{
   491  		vol0: {
   492  			Name:   vol0,
   493  			Type:   structs.VolumeTypeCSI,
   494  			Source: "/tmp/vol0",
   495  		},
   496  	}
   497  	job.TaskGroups[0].Tasks[0].VolumeMounts = []*structs.VolumeMount{
   498  		{
   499  			Volume:          vol0,
   500  			Destination:     "/var/www",
   501  			ReadOnly:        true,
   502  			PropagationMode: "private",
   503  		},
   504  	}
   505  	// if we don't set a task state, there's nothing to iterate on alloc status
   506  	alloc.TaskStates = map[string]*structs.TaskState{
   507  		"web": &structs.TaskState{
   508  			Events: []*structs.TaskEvent{
   509  				structs.NewTaskEvent("test event").SetMessage("test msg"),
   510  			},
   511  		},
   512  	}
   513  	summary := mock.JobSummary(alloc.JobID)
   514  	require.NoError(t, state.UpsertJobSummary(1004, summary))
   515  	require.NoError(t, state.UpsertAllocs(1005, []*structs.Allocation{alloc}))
   516  
   517  	ui := new(cli.MockUi)
   518  	cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
   519  	if code := cmd.Run([]string{"-address=" + url, "-verbose", alloc.ID}); code != 0 {
   520  		t.Fatalf("expected exit 0, got: %d", code)
   521  	}
   522  	out := ui.OutputWriter.String()
   523  	require.Contains(t, out, "CSI Volumes")
   524  	require.Contains(t, out, fmt.Sprintf("%s  minnie", vol0))
   525  	require.NotContains(t, out, "Host Volumes")
   526  }