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