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