github.com/hernad/nomad@v1.6.112/command/monitor_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hernad/nomad/api"
    12  	"github.com/hernad/nomad/ci"
    13  	"github.com/hernad/nomad/nomad/structs"
    14  	"github.com/mitchellh/cli"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestMonitor_Update_Eval(t *testing.T) {
    19  	ci.Parallel(t)
    20  	ui := cli.NewMockUi()
    21  	mon := newMonitor(ui, nil, fullId)
    22  
    23  	// Evals triggered by jobs log
    24  	state := &evalState{
    25  		status: structs.EvalStatusPending,
    26  		job:    "job1",
    27  	}
    28  	mon.update(state)
    29  
    30  	out := ui.OutputWriter.String()
    31  	if !strings.Contains(out, "job1") {
    32  		t.Fatalf("missing job\n\n%s", out)
    33  	}
    34  	ui.OutputWriter.Reset()
    35  
    36  	// Evals triggered by nodes log
    37  	state = &evalState{
    38  		status: structs.EvalStatusPending,
    39  		node:   "12345678-abcd-efab-cdef-123456789abc",
    40  	}
    41  	mon.update(state)
    42  
    43  	out = ui.OutputWriter.String()
    44  	if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") {
    45  		t.Fatalf("missing node\n\n%s", out)
    46  	}
    47  
    48  	// Transition to pending should not be logged
    49  	if strings.Contains(out, structs.EvalStatusPending) {
    50  		t.Fatalf("should skip status\n\n%s", out)
    51  	}
    52  	ui.OutputWriter.Reset()
    53  
    54  	// No logs sent if no update
    55  	mon.update(state)
    56  	if out := ui.OutputWriter.String(); out != "" {
    57  		t.Fatalf("expected no output\n\n%s", out)
    58  	}
    59  
    60  	// Status change sends more logs
    61  	state = &evalState{
    62  		status: structs.EvalStatusComplete,
    63  		node:   "12345678-abcd-efab-cdef-123456789abc",
    64  	}
    65  	mon.update(state)
    66  	out = ui.OutputWriter.String()
    67  	if !strings.Contains(out, structs.EvalStatusComplete) {
    68  		t.Fatalf("missing status\n\n%s", out)
    69  	}
    70  }
    71  
    72  func TestMonitor_Update_Allocs(t *testing.T) {
    73  	ci.Parallel(t)
    74  	ui := cli.NewMockUi()
    75  	mon := newMonitor(ui, nil, fullId)
    76  
    77  	// New allocations write new logs
    78  	state := &evalState{
    79  		allocs: map[string]*allocState{
    80  			"alloc1": {
    81  				id:      "87654321-abcd-efab-cdef-123456789abc",
    82  				group:   "group1",
    83  				node:    "12345678-abcd-efab-cdef-123456789abc",
    84  				desired: structs.AllocDesiredStatusRun,
    85  				client:  structs.AllocClientStatusPending,
    86  				index:   1,
    87  			},
    88  		},
    89  	}
    90  	mon.update(state)
    91  
    92  	// Logs were output
    93  	out := ui.OutputWriter.String()
    94  	if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") {
    95  		t.Fatalf("missing alloc\n\n%s", out)
    96  	}
    97  	if !strings.Contains(out, "group1") {
    98  		t.Fatalf("missing group\n\n%s", out)
    99  	}
   100  	if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") {
   101  		t.Fatalf("missing node\n\n%s", out)
   102  	}
   103  	if !strings.Contains(out, "created") {
   104  		t.Fatalf("missing created\n\n%s", out)
   105  	}
   106  	ui.OutputWriter.Reset()
   107  
   108  	// No change yields no logs
   109  	mon.update(state)
   110  	if out := ui.OutputWriter.String(); out != "" {
   111  		t.Fatalf("expected no output\n\n%s", out)
   112  	}
   113  	ui.OutputWriter.Reset()
   114  
   115  	// Alloc updates cause more log lines
   116  	state = &evalState{
   117  		allocs: map[string]*allocState{
   118  			"alloc1": {
   119  				id:      "87654321-abcd-efab-cdef-123456789abc",
   120  				group:   "group1",
   121  				node:    "12345678-abcd-efab-cdef-123456789abc",
   122  				desired: structs.AllocDesiredStatusRun,
   123  				client:  structs.AllocClientStatusRunning,
   124  				index:   2,
   125  			},
   126  		},
   127  	}
   128  	mon.update(state)
   129  
   130  	// Updates were logged
   131  	out = ui.OutputWriter.String()
   132  	if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") {
   133  		t.Fatalf("missing alloc\n\n%s", out)
   134  	}
   135  	if !strings.Contains(out, "pending") {
   136  		t.Fatalf("missing old status\n\n%s", out)
   137  	}
   138  	if !strings.Contains(out, "running") {
   139  		t.Fatalf("missing new status\n\n%s", out)
   140  	}
   141  }
   142  
   143  func TestMonitor_Update_AllocModification(t *testing.T) {
   144  	ci.Parallel(t)
   145  	ui := cli.NewMockUi()
   146  	mon := newMonitor(ui, nil, fullId)
   147  
   148  	// New allocs with a create index lower than the
   149  	// eval create index are logged as modifications
   150  	state := &evalState{
   151  		index: 2,
   152  		allocs: map[string]*allocState{
   153  			"alloc3": {
   154  				id:    "87654321-abcd-bafe-cdef-123456789abc",
   155  				node:  "12345678-abcd-efab-cdef-123456789abc",
   156  				group: "group2",
   157  				index: 1,
   158  			},
   159  		},
   160  	}
   161  	mon.update(state)
   162  
   163  	// Modification was logged
   164  	out := ui.OutputWriter.String()
   165  	if !strings.Contains(out, "87654321-abcd-bafe-cdef-123456789abc") {
   166  		t.Fatalf("missing alloc\n\n%s", out)
   167  	}
   168  	if !strings.Contains(out, "group2") {
   169  		t.Fatalf("missing group\n\n%s", out)
   170  	}
   171  	if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") {
   172  		t.Fatalf("missing node\n\n%s", out)
   173  	}
   174  	if !strings.Contains(out, "modified") {
   175  		t.Fatalf("missing modification\n\n%s", out)
   176  	}
   177  }
   178  
   179  func TestMonitor_Monitor(t *testing.T) {
   180  	ci.Parallel(t)
   181  	srv, client, _ := testServer(t, false, nil)
   182  	defer srv.Shutdown()
   183  
   184  	// Create the monitor
   185  	ui := cli.NewMockUi()
   186  	mon := newMonitor(ui, client, fullId)
   187  
   188  	// Submit a job - this creates a new evaluation we can monitor
   189  	job := testJob("job1")
   190  	resp, _, err := client.Jobs().Register(job, nil)
   191  	if err != nil {
   192  		t.Fatalf("err: %s", err)
   193  	}
   194  
   195  	// Start monitoring the eval
   196  	var code int
   197  	doneCh := make(chan struct{})
   198  	go func() {
   199  		defer close(doneCh)
   200  		code = mon.monitor(resp.EvalID)
   201  	}()
   202  
   203  	// Wait for completion
   204  	select {
   205  	case <-doneCh:
   206  	case <-time.After(5 * time.Second):
   207  		t.Fatalf("eval monitor took too long")
   208  	}
   209  
   210  	// Check the return code. We should get exit code 2 as there
   211  	// would be a scheduling problem on the test server (no clients).
   212  	if code != 2 {
   213  		t.Fatalf("expect exit 2, got: %d", code)
   214  	}
   215  
   216  	// Check the output
   217  	out := ui.OutputWriter.String()
   218  	if !strings.Contains(out, resp.EvalID) {
   219  		t.Fatalf("missing eval\n\n%s", out)
   220  	}
   221  	if !strings.Contains(out, "finished with status") {
   222  		t.Fatalf("missing final status\n\n%s", out)
   223  	}
   224  }
   225  
   226  func TestMonitor_formatAllocMetric(t *testing.T) {
   227  	ci.Parallel(t)
   228  
   229  	tests := []struct {
   230  		Name     string
   231  		Metrics  *api.AllocationMetric
   232  		Expected string
   233  	}{
   234  		{
   235  			Name: "display all possible scores",
   236  			Metrics: &api.AllocationMetric{
   237  				NodesEvaluated: 3,
   238  				NodesInPool:    3,
   239  				ScoreMetaData: []*api.NodeScoreMeta{
   240  					{
   241  						NodeID: "node-1",
   242  						Scores: map[string]float64{
   243  							"score-1": 1,
   244  							"score-2": 2,
   245  						},
   246  						NormScore: 1,
   247  					},
   248  					{
   249  						NodeID: "node-2",
   250  						Scores: map[string]float64{
   251  							"score-1": 1,
   252  							"score-3": 3,
   253  						},
   254  						NormScore: 2,
   255  					},
   256  					{
   257  						NodeID: "node-3",
   258  						Scores: map[string]float64{
   259  							"score-4": 4,
   260  						},
   261  						NormScore: 3,
   262  					},
   263  				},
   264  			},
   265  			Expected: `
   266  Node    score-1  score-2  score-3  score-4  final score
   267  node-1  1        2        0        0        1
   268  node-2  1        0        3        0        2
   269  node-3  0        0        0        4        3
   270  `,
   271  		},
   272  	}
   273  
   274  	for _, tc := range tests {
   275  		t.Run(tc.Name, func(t *testing.T) {
   276  			got := formatAllocMetrics(tc.Metrics, true, "")
   277  			require.Equal(t, strings.TrimSpace(tc.Expected), got)
   278  		})
   279  	}
   280  }