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 }