github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/command/monitor_test.go (about)

     1  package command
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/hashicorp/nomad/api"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  	"github.com/mitchellh/cli"
    11  )
    12  
    13  func TestMonitor_Update_Eval(t *testing.T) {
    14  	t.Parallel()
    15  	ui := new(cli.MockUi)
    16  	mon := newMonitor(ui, nil, fullId)
    17  
    18  	// Evals triggered by jobs log
    19  	state := &evalState{
    20  		status: structs.EvalStatusPending,
    21  		job:    "job1",
    22  	}
    23  	mon.update(state)
    24  
    25  	out := ui.OutputWriter.String()
    26  	if !strings.Contains(out, "job1") {
    27  		t.Fatalf("missing job\n\n%s", out)
    28  	}
    29  	ui.OutputWriter.Reset()
    30  
    31  	// Evals trigerred by nodes log
    32  	state = &evalState{
    33  		status: structs.EvalStatusPending,
    34  		node:   "12345678-abcd-efab-cdef-123456789abc",
    35  	}
    36  	mon.update(state)
    37  
    38  	out = ui.OutputWriter.String()
    39  	if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") {
    40  		t.Fatalf("missing node\n\n%s", out)
    41  	}
    42  
    43  	// Transition to pending should not be logged
    44  	if strings.Contains(out, structs.EvalStatusPending) {
    45  		t.Fatalf("should skip status\n\n%s", out)
    46  	}
    47  	ui.OutputWriter.Reset()
    48  
    49  	// No logs sent if no update
    50  	mon.update(state)
    51  	if out := ui.OutputWriter.String(); out != "" {
    52  		t.Fatalf("expected no output\n\n%s", out)
    53  	}
    54  
    55  	// Status change sends more logs
    56  	state = &evalState{
    57  		status: structs.EvalStatusComplete,
    58  		node:   "12345678-abcd-efab-cdef-123456789abc",
    59  	}
    60  	mon.update(state)
    61  	out = ui.OutputWriter.String()
    62  	if !strings.Contains(out, structs.EvalStatusComplete) {
    63  		t.Fatalf("missing status\n\n%s", out)
    64  	}
    65  }
    66  
    67  func TestMonitor_Update_Allocs(t *testing.T) {
    68  	t.Parallel()
    69  	ui := new(cli.MockUi)
    70  	mon := newMonitor(ui, nil, fullId)
    71  
    72  	// New allocations write new logs
    73  	state := &evalState{
    74  		allocs: map[string]*allocState{
    75  			"alloc1": &allocState{
    76  				id:      "87654321-abcd-efab-cdef-123456789abc",
    77  				group:   "group1",
    78  				node:    "12345678-abcd-efab-cdef-123456789abc",
    79  				desired: structs.AllocDesiredStatusRun,
    80  				client:  structs.AllocClientStatusPending,
    81  				index:   1,
    82  			},
    83  		},
    84  	}
    85  	mon.update(state)
    86  
    87  	// Logs were output
    88  	out := ui.OutputWriter.String()
    89  	if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") {
    90  		t.Fatalf("missing alloc\n\n%s", out)
    91  	}
    92  	if !strings.Contains(out, "group1") {
    93  		t.Fatalf("missing group\n\n%s", out)
    94  	}
    95  	if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") {
    96  		t.Fatalf("missing node\n\n%s", out)
    97  	}
    98  	if !strings.Contains(out, "created") {
    99  		t.Fatalf("missing created\n\n%s", out)
   100  	}
   101  	ui.OutputWriter.Reset()
   102  
   103  	// No change yields no logs
   104  	mon.update(state)
   105  	if out := ui.OutputWriter.String(); out != "" {
   106  		t.Fatalf("expected no output\n\n%s", out)
   107  	}
   108  	ui.OutputWriter.Reset()
   109  
   110  	// Alloc updates cause more log lines
   111  	state = &evalState{
   112  		allocs: map[string]*allocState{
   113  			"alloc1": &allocState{
   114  				id:      "87654321-abcd-efab-cdef-123456789abc",
   115  				group:   "group1",
   116  				node:    "12345678-abcd-efab-cdef-123456789abc",
   117  				desired: structs.AllocDesiredStatusRun,
   118  				client:  structs.AllocClientStatusRunning,
   119  				index:   2,
   120  			},
   121  		},
   122  	}
   123  	mon.update(state)
   124  
   125  	// Updates were logged
   126  	out = ui.OutputWriter.String()
   127  	if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") {
   128  		t.Fatalf("missing alloc\n\n%s", out)
   129  	}
   130  	if !strings.Contains(out, "pending") {
   131  		t.Fatalf("missing old status\n\n%s", out)
   132  	}
   133  	if !strings.Contains(out, "running") {
   134  		t.Fatalf("missing new status\n\n%s", out)
   135  	}
   136  }
   137  
   138  func TestMonitor_Update_AllocModification(t *testing.T) {
   139  	t.Parallel()
   140  	ui := new(cli.MockUi)
   141  	mon := newMonitor(ui, nil, fullId)
   142  
   143  	// New allocs with a create index lower than the
   144  	// eval create index are logged as modifications
   145  	state := &evalState{
   146  		index: 2,
   147  		allocs: map[string]*allocState{
   148  			"alloc3": &allocState{
   149  				id:    "87654321-abcd-bafe-cdef-123456789abc",
   150  				node:  "12345678-abcd-efab-cdef-123456789abc",
   151  				group: "group2",
   152  				index: 1,
   153  			},
   154  		},
   155  	}
   156  	mon.update(state)
   157  
   158  	// Modification was logged
   159  	out := ui.OutputWriter.String()
   160  	if !strings.Contains(out, "87654321-abcd-bafe-cdef-123456789abc") {
   161  		t.Fatalf("missing alloc\n\n%s", out)
   162  	}
   163  	if !strings.Contains(out, "group2") {
   164  		t.Fatalf("missing group\n\n%s", out)
   165  	}
   166  	if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") {
   167  		t.Fatalf("missing node\n\n%s", out)
   168  	}
   169  	if !strings.Contains(out, "modified") {
   170  		t.Fatalf("missing modification\n\n%s", out)
   171  	}
   172  }
   173  
   174  func TestMonitor_Monitor(t *testing.T) {
   175  	t.Parallel()
   176  	srv, client, _ := testServer(t, false, nil)
   177  	defer srv.Shutdown()
   178  
   179  	// Create the monitor
   180  	ui := new(cli.MockUi)
   181  	mon := newMonitor(ui, client, fullId)
   182  
   183  	// Submit a job - this creates a new evaluation we can monitor
   184  	job := testJob("job1")
   185  	resp, _, err := client.Jobs().Register(job, nil)
   186  	if err != nil {
   187  		t.Fatalf("err: %s", err)
   188  	}
   189  
   190  	// Start monitoring the eval
   191  	var code int
   192  	doneCh := make(chan struct{})
   193  	go func() {
   194  		defer close(doneCh)
   195  		code = mon.monitor(resp.EvalID, false)
   196  	}()
   197  
   198  	// Wait for completion
   199  	select {
   200  	case <-doneCh:
   201  	case <-time.After(5 * time.Second):
   202  		t.Fatalf("eval monitor took too long")
   203  	}
   204  
   205  	// Check the return code. We should get exit code 2 as there
   206  	// would be a scheduling problem on the test server (no clients).
   207  	if code != 2 {
   208  		t.Fatalf("expect exit 2, got: %d", code)
   209  	}
   210  
   211  	// Check the output
   212  	out := ui.OutputWriter.String()
   213  	if !strings.Contains(out, resp.EvalID) {
   214  		t.Fatalf("missing eval\n\n%s", out)
   215  	}
   216  	if !strings.Contains(out, "finished with status") {
   217  		t.Fatalf("missing final status\n\n%s", out)
   218  	}
   219  }
   220  
   221  func TestMonitor_MonitorWithPrefix(t *testing.T) {
   222  	t.Parallel()
   223  	srv, client, _ := testServer(t, false, nil)
   224  	defer srv.Shutdown()
   225  
   226  	// Create the monitor
   227  	ui := new(cli.MockUi)
   228  	mon := newMonitor(ui, client, shortId)
   229  
   230  	// Submit a job - this creates a new evaluation we can monitor
   231  	job := testJob("job1")
   232  	resp, _, err := client.Jobs().Register(job, nil)
   233  	if err != nil {
   234  		t.Fatalf("err: %s", err)
   235  	}
   236  
   237  	// Start monitoring the eval
   238  	var code int
   239  	doneCh := make(chan struct{})
   240  	go func() {
   241  		defer close(doneCh)
   242  		code = mon.monitor(resp.EvalID[:8], true)
   243  	}()
   244  
   245  	// Wait for completion
   246  	select {
   247  	case <-doneCh:
   248  	case <-time.After(5 * time.Second):
   249  		t.Fatalf("eval monitor took too long")
   250  	}
   251  
   252  	// Check the return code. We should get exit code 2 as there
   253  	// would be a scheduling problem on the test server (no clients).
   254  	if code != 2 {
   255  		t.Fatalf("expect exit 2, got: %d", code)
   256  	}
   257  
   258  	// Check the output
   259  	out := ui.OutputWriter.String()
   260  	if !strings.Contains(out, resp.EvalID[:8]) {
   261  		t.Fatalf("missing eval\n\n%s", out)
   262  	}
   263  	if strings.Contains(out, resp.EvalID) {
   264  		t.Fatalf("expected truncated eval id, got: %s", out)
   265  	}
   266  	if !strings.Contains(out, "finished with status") {
   267  		t.Fatalf("missing final status\n\n%s", out)
   268  	}
   269  
   270  	// Fail on identifier with too few characters
   271  	code = mon.monitor(resp.EvalID[:1], true)
   272  	if code != 1 {
   273  		t.Fatalf("expect exit 1, got: %d", code)
   274  	}
   275  	if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") {
   276  		t.Fatalf("expected too few characters error, got: %s", out)
   277  	}
   278  	ui.ErrorWriter.Reset()
   279  
   280  	code = mon.monitor(resp.EvalID[:3], true)
   281  	if code != 2 {
   282  		t.Fatalf("expect exit 2, got: %d", code)
   283  	}
   284  	if out := ui.OutputWriter.String(); !strings.Contains(out, "Monitoring evaluation") {
   285  		t.Fatalf("expected evaluation monitoring output, got: %s", out)
   286  	}
   287  
   288  }
   289  
   290  func TestMonitor_DumpAllocStatus(t *testing.T) {
   291  	t.Parallel()
   292  	ui := new(cli.MockUi)
   293  
   294  	// Create an allocation and dump its status to the UI
   295  	alloc := &api.Allocation{
   296  		ID:           "87654321-abcd-efab-cdef-123456789abc",
   297  		TaskGroup:    "group1",
   298  		ClientStatus: structs.AllocClientStatusRunning,
   299  		Metrics: &api.AllocationMetric{
   300  			NodesEvaluated: 10,
   301  			NodesFiltered:  5,
   302  			NodesExhausted: 1,
   303  			DimensionExhausted: map[string]int{
   304  				"cpu": 1,
   305  			},
   306  			ConstraintFiltered: map[string]int{
   307  				"$attr.kernel.name = linux": 1,
   308  			},
   309  			ClassExhausted: map[string]int{
   310  				"web-large": 1,
   311  			},
   312  		},
   313  	}
   314  	dumpAllocStatus(ui, alloc, fullId)
   315  
   316  	// Check the output
   317  	out := ui.OutputWriter.String()
   318  	if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") {
   319  		t.Fatalf("missing alloc\n\n%s", out)
   320  	}
   321  	if !strings.Contains(out, structs.AllocClientStatusRunning) {
   322  		t.Fatalf("missing status\n\n%s", out)
   323  	}
   324  	if !strings.Contains(out, "5/10") {
   325  		t.Fatalf("missing filter stats\n\n%s", out)
   326  	}
   327  	if !strings.Contains(
   328  		out, `Constraint "$attr.kernel.name = linux" filtered 1 nodes`) {
   329  		t.Fatalf("missing constraint\n\n%s", out)
   330  	}
   331  	if !strings.Contains(out, "Resources exhausted on 1 nodes") {
   332  		t.Fatalf("missing resource exhaustion\n\n%s", out)
   333  	}
   334  	if !strings.Contains(out, `Class "web-large" exhausted on 1 nodes`) {
   335  		t.Fatalf("missing class exhaustion\n\n%s", out)
   336  	}
   337  	if !strings.Contains(out, `Dimension "cpu" exhausted on 1 nodes`) {
   338  		t.Fatalf("missing dimension exhaustion\n\n%s", out)
   339  	}
   340  	ui.OutputWriter.Reset()
   341  
   342  	// Dumping alloc status with no eligible nodes adds a warning
   343  	alloc.Metrics.NodesEvaluated = 0
   344  	dumpAllocStatus(ui, alloc, shortId)
   345  
   346  	// Check the output
   347  	out = ui.OutputWriter.String()
   348  	if !strings.Contains(out, "No nodes were eligible") {
   349  		t.Fatalf("missing eligibility warning\n\n%s", out)
   350  	}
   351  	if strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") {
   352  		t.Fatalf("expected truncated id, got %s", out)
   353  	}
   354  	if !strings.Contains(out, "87654321") {
   355  		t.Fatalf("expected alloc id, got %s", out)
   356  	}
   357  }