github.com/djenriquez/nomad-1@v0.8.1/command/job_status_test.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/hashicorp/nomad/command/agent"
    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 TestJobStatusCommand_Implements(t *testing.T) {
    21  	t.Parallel()
    22  	var _ cli.Command = &JobStatusCommand{}
    23  }
    24  
    25  func TestJobStatusCommand_Run(t *testing.T) {
    26  	t.Parallel()
    27  	srv, client, url := testServer(t, true, nil)
    28  	defer srv.Shutdown()
    29  
    30  	ui := new(cli.MockUi)
    31  	cmd := &JobStatusCommand{Meta: Meta{Ui: ui}}
    32  
    33  	// Should return blank for no jobs
    34  	if code := cmd.Run([]string{"-address=" + url}); code != 0 {
    35  		t.Fatalf("expected exit 0, got: %d", code)
    36  	}
    37  
    38  	// Check for this awkward nil string, since a nil bytes.Buffer
    39  	// returns this purposely, and mitchellh/cli has a nil pointer
    40  	// if nothing was ever output.
    41  	exp := "No running jobs"
    42  	if out := strings.TrimSpace(ui.OutputWriter.String()); out != exp {
    43  		t.Fatalf("expected %q; got: %q", exp, out)
    44  	}
    45  
    46  	// Register two jobs
    47  	job1 := testJob("job1_sfx")
    48  	resp, _, err := client.Jobs().Register(job1, nil)
    49  	if err != nil {
    50  		t.Fatalf("err: %s", err)
    51  	}
    52  	if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
    53  		t.Fatalf("status code non zero saw %d", code)
    54  	}
    55  
    56  	job2 := testJob("job2_sfx")
    57  	resp2, _, err := client.Jobs().Register(job2, nil)
    58  	if err != nil {
    59  		t.Fatalf("err: %s", err)
    60  	}
    61  	if code := waitForSuccess(ui, client, fullId, t, resp2.EvalID); code != 0 {
    62  		t.Fatalf("status code non zero saw %d", code)
    63  	}
    64  
    65  	// Query again and check the result
    66  	if code := cmd.Run([]string{"-address=" + url}); code != 0 {
    67  		t.Fatalf("expected exit 0, got: %d", code)
    68  	}
    69  	out := ui.OutputWriter.String()
    70  	if !strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") {
    71  		t.Fatalf("expected job1_sfx and job2_sfx, got: %s", out)
    72  	}
    73  	ui.OutputWriter.Reset()
    74  
    75  	// Query a single job
    76  	if code := cmd.Run([]string{"-address=" + url, "job2_sfx"}); code != 0 {
    77  		t.Fatalf("expected exit 0, got: %d", code)
    78  	}
    79  	out = ui.OutputWriter.String()
    80  	if strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") {
    81  		t.Fatalf("expected only job2_sfx, got: %s", out)
    82  	}
    83  	if !strings.Contains(out, "Allocations") {
    84  		t.Fatalf("should dump allocations")
    85  	}
    86  	if !strings.Contains(out, "Summary") {
    87  		t.Fatalf("should dump summary")
    88  	}
    89  	ui.OutputWriter.Reset()
    90  
    91  	// Query a single job showing evals
    92  	if code := cmd.Run([]string{"-address=" + url, "-evals", "job2_sfx"}); code != 0 {
    93  		t.Fatalf("expected exit 0, got: %d", code)
    94  	}
    95  	out = ui.OutputWriter.String()
    96  	if strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") {
    97  		t.Fatalf("expected only job2_sfx, got: %s", out)
    98  	}
    99  	if !strings.Contains(out, "Evaluations") {
   100  		t.Fatalf("should dump evaluations")
   101  	}
   102  	if !strings.Contains(out, "Allocations") {
   103  		t.Fatalf("should dump allocations")
   104  	}
   105  	ui.OutputWriter.Reset()
   106  
   107  	// Query a single job in verbose mode
   108  	if code := cmd.Run([]string{"-address=" + url, "-verbose", "job2_sfx"}); code != 0 {
   109  		t.Fatalf("expected exit 0, got: %d", code)
   110  	}
   111  	out = ui.OutputWriter.String()
   112  	if strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") {
   113  		t.Fatalf("expected only job2_sfx, got: %s", out)
   114  	}
   115  	if !strings.Contains(out, "Evaluations") {
   116  		t.Fatalf("should dump evaluations")
   117  	}
   118  	if !strings.Contains(out, "Allocations") {
   119  		t.Fatalf("should dump allocations")
   120  	}
   121  	if !strings.Contains(out, "Created") {
   122  		t.Fatal("should have created header")
   123  	}
   124  	if !strings.Contains(out, "Modified") {
   125  		t.Fatal("should have modified header")
   126  	}
   127  	ui.ErrorWriter.Reset()
   128  	ui.OutputWriter.Reset()
   129  
   130  	// Query jobs with prefix match
   131  	if code := cmd.Run([]string{"-address=" + url, "job"}); code != 1 {
   132  		t.Fatalf("expected exit 1, got: %d", code)
   133  	}
   134  	out = ui.ErrorWriter.String()
   135  	if !strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") {
   136  		t.Fatalf("expected job1_sfx and job2_sfx, got: %s", out)
   137  	}
   138  	ui.ErrorWriter.Reset()
   139  	ui.OutputWriter.Reset()
   140  
   141  	// Query a single job with prefix match
   142  	if code := cmd.Run([]string{"-address=" + url, "job1"}); code != 0 {
   143  		t.Fatalf("expected exit 0, got: %d", code)
   144  	}
   145  	out = ui.OutputWriter.String()
   146  	if !strings.Contains(out, "job1_sfx") || strings.Contains(out, "job2_sfx") {
   147  		t.Fatalf("expected only job1_sfx, got: %s", out)
   148  	}
   149  
   150  	if !strings.Contains(out, "Created") {
   151  		t.Fatal("should have created header")
   152  	}
   153  
   154  	if !strings.Contains(out, "Modified") {
   155  		t.Fatal("should have modified header")
   156  	}
   157  	ui.OutputWriter.Reset()
   158  
   159  	// Query in short view mode
   160  	if code := cmd.Run([]string{"-address=" + url, "-short", "job2"}); code != 0 {
   161  		t.Fatalf("expected exit 0, got: %d", code)
   162  	}
   163  	out = ui.OutputWriter.String()
   164  	if !strings.Contains(out, "job2") {
   165  		t.Fatalf("expected job2, got: %s", out)
   166  	}
   167  	if strings.Contains(out, "Evaluations") {
   168  		t.Fatalf("should not dump evaluations")
   169  	}
   170  	if strings.Contains(out, "Allocations") {
   171  		t.Fatalf("should not dump allocations")
   172  	}
   173  	if strings.Contains(out, resp.EvalID) {
   174  		t.Fatalf("should not contain full identifiers, got %s", out)
   175  	}
   176  	ui.OutputWriter.Reset()
   177  
   178  	// Request full identifiers
   179  	if code := cmd.Run([]string{"-address=" + url, "-verbose", "job1"}); code != 0 {
   180  		t.Fatalf("expected exit 0, got: %d", code)
   181  	}
   182  	out = ui.OutputWriter.String()
   183  	if !strings.Contains(out, resp.EvalID) {
   184  		t.Fatalf("should contain full identifiers, got %s", out)
   185  	}
   186  }
   187  
   188  func TestJobStatusCommand_Fails(t *testing.T) {
   189  	t.Parallel()
   190  	ui := new(cli.MockUi)
   191  	cmd := &JobStatusCommand{Meta: Meta{Ui: ui}}
   192  
   193  	// Fails on misuse
   194  	if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 {
   195  		t.Fatalf("expected exit code 1, got: %d", code)
   196  	}
   197  	if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) {
   198  		t.Fatalf("expected help output, got: %s", out)
   199  	}
   200  	ui.ErrorWriter.Reset()
   201  
   202  	// Fails on connection failure
   203  	if code := cmd.Run([]string{"-address=nope"}); code != 1 {
   204  		t.Fatalf("expected exit code 1, got: %d", code)
   205  	}
   206  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying jobs") {
   207  		t.Fatalf("expected failed query error, got: %s", out)
   208  	}
   209  }
   210  
   211  func TestJobStatusCommand_AutocompleteArgs(t *testing.T) {
   212  	assert := assert.New(t)
   213  	t.Parallel()
   214  
   215  	srv, _, url := testServer(t, true, nil)
   216  	defer srv.Shutdown()
   217  
   218  	ui := new(cli.MockUi)
   219  	cmd := &JobStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
   220  
   221  	// Create a fake job
   222  	state := srv.Agent.Server().State()
   223  	j := mock.Job()
   224  	assert.Nil(state.UpsertJob(1000, j))
   225  
   226  	prefix := j.ID[:len(j.ID)-5]
   227  	args := complete.Args{Last: prefix}
   228  	predictor := cmd.AutocompleteArgs()
   229  
   230  	res := predictor.Predict(args)
   231  	assert.Equal(1, len(res))
   232  	assert.Equal(j.ID, res[0])
   233  }
   234  
   235  func TestJobStatusCommand_WithAccessPolicy(t *testing.T) {
   236  	assert := assert.New(t)
   237  	t.Parallel()
   238  
   239  	config := func(c *agent.Config) {
   240  		c.ACL.Enabled = true
   241  	}
   242  
   243  	srv, client, url := testServer(t, true, config)
   244  	defer srv.Shutdown()
   245  
   246  	// Bootstrap an initial ACL token
   247  	token := srv.RootToken
   248  	assert.NotNil(token, "failed to bootstrap ACL token")
   249  
   250  	// Register a job
   251  	j := testJob("job1_sfx")
   252  
   253  	invalidToken := mock.ACLToken()
   254  
   255  	ui := new(cli.MockUi)
   256  	cmd := &JobStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
   257  
   258  	// registering a job without a token fails
   259  	client.SetSecretID(invalidToken.SecretID)
   260  	resp, _, err := client.Jobs().Register(j, nil)
   261  	assert.NotNil(err)
   262  
   263  	// registering a job with a valid token succeeds
   264  	client.SetSecretID(token.SecretID)
   265  	resp, _, err = client.Jobs().Register(j, nil)
   266  	assert.Nil(err)
   267  	code := waitForSuccess(ui, client, fullId, t, resp.EvalID)
   268  	assert.Equal(0, code)
   269  
   270  	// Request Job List without providing a valid token
   271  	code = cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID, "-short"})
   272  	assert.Equal(1, code)
   273  
   274  	// Request Job List with a valid token
   275  	code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-short"})
   276  	assert.Equal(0, code)
   277  
   278  	out := ui.OutputWriter.String()
   279  	if !strings.Contains(out, *j.ID) {
   280  		t.Fatalf("should contain full identifiers, got %s", out)
   281  	}
   282  }
   283  
   284  func TestJobStatusCommand_RescheduleEvals(t *testing.T) {
   285  	t.Parallel()
   286  	srv, client, url := testServer(t, true, nil)
   287  	defer srv.Shutdown()
   288  
   289  	// Wait for a node to be ready
   290  	testutil.WaitForResult(func() (bool, error) {
   291  		nodes, _, err := client.Nodes().List(nil)
   292  		if err != nil {
   293  			return false, err
   294  		}
   295  		for _, node := range nodes {
   296  			if node.Status == structs.NodeStatusReady {
   297  				return true, nil
   298  			}
   299  		}
   300  		return false, fmt.Errorf("no ready nodes")
   301  	}, func(err error) {
   302  		t.Fatalf("err: %v", err)
   303  	})
   304  
   305  	ui := new(cli.MockUi)
   306  	cmd := &JobStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
   307  
   308  	require := require.New(t)
   309  	state := srv.Agent.Server().State()
   310  
   311  	// Create state store objects for job, alloc and followup eval with a future WaitUntil value
   312  	j := mock.Job()
   313  	require.Nil(state.UpsertJob(900, j))
   314  
   315  	e := mock.Eval()
   316  	e.WaitUntil = time.Now().Add(1 * time.Hour)
   317  	require.Nil(state.UpsertEvals(902, []*structs.Evaluation{e}))
   318  	a := mock.Alloc()
   319  	a.Job = j
   320  	a.JobID = j.ID
   321  	a.TaskGroup = j.TaskGroups[0].Name
   322  	a.FollowupEvalID = e.ID
   323  	a.Metrics = &structs.AllocMetric{}
   324  	a.DesiredStatus = structs.AllocDesiredStatusRun
   325  	a.ClientStatus = structs.AllocClientStatusRunning
   326  	require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a}))
   327  
   328  	// Query jobs with prefix match
   329  	if code := cmd.Run([]string{"-address=" + url, j.ID}); code != 0 {
   330  		t.Fatalf("expected exit 0, got: %d", code)
   331  	}
   332  	out := ui.OutputWriter.String()
   333  	require.Contains(out, "Future Rescheduling Attempts")
   334  	require.Contains(out, e.ID[:8])
   335  }
   336  
   337  func waitForSuccess(ui cli.Ui, client *api.Client, length int, t *testing.T, evalId string) int {
   338  	mon := newMonitor(ui, client, length)
   339  	monErr := mon.monitor(evalId, false)
   340  	return monErr
   341  }