github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/nomad/worker_test.go (about) 1 package nomad 2 3 import ( 4 "log" 5 "reflect" 6 "strings" 7 "sync" 8 "testing" 9 "time" 10 11 memdb "github.com/hashicorp/go-memdb" 12 "github.com/hashicorp/nomad/nomad/mock" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "github.com/hashicorp/nomad/scheduler" 15 "github.com/hashicorp/nomad/testutil" 16 ) 17 18 type NoopScheduler struct { 19 state scheduler.State 20 planner scheduler.Planner 21 eval *structs.Evaluation 22 err error 23 } 24 25 func (n *NoopScheduler) Process(eval *structs.Evaluation) error { 26 if n.state == nil { 27 panic("missing state") 28 } 29 if n.planner == nil { 30 panic("missing planner") 31 } 32 n.eval = eval 33 return n.err 34 } 35 36 func init() { 37 scheduler.BuiltinSchedulers["noop"] = func(logger *log.Logger, s scheduler.State, p scheduler.Planner) scheduler.Scheduler { 38 n := &NoopScheduler{ 39 state: s, 40 planner: p, 41 } 42 return n 43 } 44 } 45 46 func TestWorker_dequeueEvaluation(t *testing.T) { 47 t.Parallel() 48 s1 := testServer(t, func(c *Config) { 49 c.NumSchedulers = 0 50 c.EnabledSchedulers = []string{structs.JobTypeService} 51 }) 52 defer s1.Shutdown() 53 testutil.WaitForLeader(t, s1.RPC) 54 55 // Create the evaluation 56 eval1 := mock.Eval() 57 s1.evalBroker.Enqueue(eval1) 58 59 // Create a worker 60 w := &Worker{srv: s1, logger: s1.logger} 61 62 // Attempt dequeue 63 eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 64 if shutdown { 65 t.Fatalf("should not shutdown") 66 } 67 if token == "" { 68 t.Fatalf("should get token") 69 } 70 71 // Ensure we get a sane eval 72 if !reflect.DeepEqual(eval, eval1) { 73 t.Fatalf("bad: %#v %#v", eval, eval1) 74 } 75 } 76 77 func TestWorker_dequeueEvaluation_paused(t *testing.T) { 78 t.Parallel() 79 s1 := testServer(t, func(c *Config) { 80 c.NumSchedulers = 0 81 c.EnabledSchedulers = []string{structs.JobTypeService} 82 }) 83 defer s1.Shutdown() 84 testutil.WaitForLeader(t, s1.RPC) 85 86 // Create the evaluation 87 eval1 := mock.Eval() 88 s1.evalBroker.Enqueue(eval1) 89 90 // Create a worker 91 w := &Worker{srv: s1, logger: s1.logger} 92 w.pauseCond = sync.NewCond(&w.pauseLock) 93 94 // PAUSE the worker 95 w.SetPause(true) 96 97 go func() { 98 time.Sleep(100 * time.Millisecond) 99 w.SetPause(false) 100 }() 101 102 // Attempt dequeue 103 start := time.Now() 104 eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 105 if diff := time.Since(start); diff < 100*time.Millisecond { 106 t.Fatalf("should have paused: %v", diff) 107 } 108 if shutdown { 109 t.Fatalf("should not shutdown") 110 } 111 if token == "" { 112 t.Fatalf("should get token") 113 } 114 115 // Ensure we get a sane eval 116 if !reflect.DeepEqual(eval, eval1) { 117 t.Fatalf("bad: %#v %#v", eval, eval1) 118 } 119 } 120 121 func TestWorker_dequeueEvaluation_shutdown(t *testing.T) { 122 t.Parallel() 123 s1 := testServer(t, func(c *Config) { 124 c.NumSchedulers = 0 125 c.EnabledSchedulers = []string{structs.JobTypeService} 126 }) 127 defer s1.Shutdown() 128 testutil.WaitForLeader(t, s1.RPC) 129 130 // Create a worker 131 w := &Worker{srv: s1, logger: s1.logger} 132 133 go func() { 134 time.Sleep(10 * time.Millisecond) 135 s1.Shutdown() 136 }() 137 138 // Attempt dequeue 139 eval, _, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 140 if !shutdown { 141 t.Fatalf("should not shutdown") 142 } 143 144 // Ensure we get a sane eval 145 if eval != nil { 146 t.Fatalf("bad: %#v", eval) 147 } 148 } 149 150 func TestWorker_sendAck(t *testing.T) { 151 t.Parallel() 152 s1 := testServer(t, func(c *Config) { 153 c.NumSchedulers = 0 154 c.EnabledSchedulers = []string{structs.JobTypeService} 155 }) 156 defer s1.Shutdown() 157 testutil.WaitForLeader(t, s1.RPC) 158 159 // Create the evaluation 160 eval1 := mock.Eval() 161 s1.evalBroker.Enqueue(eval1) 162 163 // Create a worker 164 w := &Worker{srv: s1, logger: s1.logger} 165 166 // Attempt dequeue 167 eval, token, _ := w.dequeueEvaluation(10 * time.Millisecond) 168 169 // Check the depth is 0, 1 unacked 170 stats := s1.evalBroker.Stats() 171 if stats.TotalReady != 0 && stats.TotalUnacked != 1 { 172 t.Fatalf("bad: %#v", stats) 173 } 174 175 // Send the Nack 176 w.sendAck(eval.ID, token, false) 177 178 // Check the depth is 1, nothing unacked 179 stats = s1.evalBroker.Stats() 180 if stats.TotalReady != 1 && stats.TotalUnacked != 0 { 181 t.Fatalf("bad: %#v", stats) 182 } 183 184 // Attempt dequeue 185 eval, token, _ = w.dequeueEvaluation(10 * time.Millisecond) 186 187 // Send the Ack 188 w.sendAck(eval.ID, token, true) 189 190 // Check the depth is 0 191 stats = s1.evalBroker.Stats() 192 if stats.TotalReady != 0 && stats.TotalUnacked != 0 { 193 t.Fatalf("bad: %#v", stats) 194 } 195 } 196 197 func TestWorker_waitForIndex(t *testing.T) { 198 t.Parallel() 199 s1 := testServer(t, func(c *Config) { 200 c.NumSchedulers = 0 201 c.EnabledSchedulers = []string{structs.JobTypeService} 202 }) 203 defer s1.Shutdown() 204 testutil.WaitForLeader(t, s1.RPC) 205 206 // Get the current index 207 index := s1.raft.AppliedIndex() 208 209 // Cause an increment 210 go func() { 211 time.Sleep(10 * time.Millisecond) 212 n := mock.Node() 213 if err := s1.fsm.state.UpsertNode(index+1, n); err != nil { 214 t.Fatalf("failed to upsert node: %v", err) 215 } 216 }() 217 218 // Wait for a future index 219 w := &Worker{srv: s1, logger: s1.logger} 220 err := w.waitForIndex(index+1, time.Second) 221 if err != nil { 222 t.Fatalf("err: %v", err) 223 } 224 225 // Cause a timeout 226 err = w.waitForIndex(index+100, 10*time.Millisecond) 227 if err == nil || !strings.Contains(err.Error(), "timeout") { 228 t.Fatalf("err: %v", err) 229 } 230 } 231 232 func TestWorker_invokeScheduler(t *testing.T) { 233 t.Parallel() 234 s1 := testServer(t, func(c *Config) { 235 c.NumSchedulers = 0 236 c.EnabledSchedulers = []string{structs.JobTypeService} 237 }) 238 defer s1.Shutdown() 239 240 w := &Worker{srv: s1, logger: s1.logger} 241 eval := mock.Eval() 242 eval.Type = "noop" 243 244 err := w.invokeScheduler(eval, structs.GenerateUUID()) 245 if err != nil { 246 t.Fatalf("err: %v", err) 247 } 248 } 249 250 func TestWorker_SubmitPlan(t *testing.T) { 251 t.Parallel() 252 s1 := testServer(t, func(c *Config) { 253 c.NumSchedulers = 0 254 c.EnabledSchedulers = []string{structs.JobTypeService} 255 }) 256 defer s1.Shutdown() 257 testutil.WaitForLeader(t, s1.RPC) 258 259 // Register node 260 node := mock.Node() 261 testRegisterNode(t, s1, node) 262 263 eval1 := mock.Eval() 264 s1.fsm.State().UpsertJobSummary(1000, mock.JobSummary(eval1.JobID)) 265 266 // Create the register request 267 s1.evalBroker.Enqueue(eval1) 268 269 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 270 if err != nil { 271 t.Fatalf("err: %v", err) 272 } 273 if evalOut != eval1 { 274 t.Fatalf("Bad eval") 275 } 276 277 // Create an allocation plan 278 alloc := mock.Alloc() 279 s1.fsm.State().UpsertJobSummary(1200, mock.JobSummary(alloc.JobID)) 280 plan := &structs.Plan{ 281 EvalID: eval1.ID, 282 NodeAllocation: map[string][]*structs.Allocation{ 283 node.ID: []*structs.Allocation{alloc}, 284 }, 285 } 286 287 // Attempt to submit a plan 288 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 289 result, state, err := w.SubmitPlan(plan) 290 if err != nil { 291 t.Fatalf("err: %v", err) 292 } 293 294 // Should have no update 295 if state != nil { 296 t.Fatalf("unexpected state update") 297 } 298 299 // Result should have allocated 300 if result == nil { 301 t.Fatalf("missing result") 302 } 303 304 if result.AllocIndex == 0 { 305 t.Fatalf("Bad: %#v", result) 306 } 307 if len(result.NodeAllocation) != 1 { 308 t.Fatalf("Bad: %#v", result) 309 } 310 } 311 312 func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) { 313 t.Parallel() 314 s1 := testServer(t, func(c *Config) { 315 c.NumSchedulers = 0 316 c.EnabledSchedulers = []string{structs.JobTypeService} 317 }) 318 defer s1.Shutdown() 319 testutil.WaitForLeader(t, s1.RPC) 320 321 // Register node 322 node := mock.Node() 323 testRegisterNode(t, s1, node) 324 325 // Create the register request 326 eval1 := mock.Eval() 327 s1.evalBroker.Enqueue(eval1) 328 329 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 330 if err != nil { 331 t.Fatalf("err: %v", err) 332 } 333 if evalOut != eval1 { 334 t.Fatalf("Bad eval") 335 } 336 337 // Create an allocation plan, with unregistered node 338 node2 := mock.Node() 339 alloc := mock.Alloc() 340 plan := &structs.Plan{ 341 EvalID: eval1.ID, 342 NodeAllocation: map[string][]*structs.Allocation{ 343 node2.ID: []*structs.Allocation{alloc}, 344 }, 345 } 346 347 // Attempt to submit a plan 348 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 349 result, state, err := w.SubmitPlan(plan) 350 if err != nil { 351 t.Fatalf("err: %v", err) 352 } 353 354 // Result should have allocated 355 if result == nil { 356 t.Fatalf("missing result") 357 } 358 359 // Expect no allocation and forced refresh 360 if result.AllocIndex != 0 { 361 t.Fatalf("Bad: %#v", result) 362 } 363 if result.RefreshIndex == 0 { 364 t.Fatalf("Bad: %#v", result) 365 } 366 if len(result.NodeAllocation) != 0 { 367 t.Fatalf("Bad: %#v", result) 368 } 369 370 // Should have an update 371 if state == nil { 372 t.Fatalf("expected state update") 373 } 374 } 375 376 func TestWorker_UpdateEval(t *testing.T) { 377 t.Parallel() 378 s1 := testServer(t, func(c *Config) { 379 c.NumSchedulers = 0 380 c.EnabledSchedulers = []string{structs.JobTypeService} 381 }) 382 defer s1.Shutdown() 383 testutil.WaitForLeader(t, s1.RPC) 384 385 // Register node 386 node := mock.Node() 387 testRegisterNode(t, s1, node) 388 389 // Create the register request 390 eval1 := mock.Eval() 391 s1.evalBroker.Enqueue(eval1) 392 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 393 if err != nil { 394 t.Fatalf("err: %v", err) 395 } 396 if evalOut != eval1 { 397 t.Fatalf("Bad eval") 398 } 399 400 eval2 := evalOut.Copy() 401 eval2.Status = structs.EvalStatusComplete 402 403 // Attempt to update eval 404 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 405 err = w.UpdateEval(eval2) 406 if err != nil { 407 t.Fatalf("err: %v", err) 408 } 409 410 ws := memdb.NewWatchSet() 411 out, err := s1.fsm.State().EvalByID(ws, eval2.ID) 412 if err != nil { 413 t.Fatalf("err: %v", err) 414 } 415 if out.Status != structs.EvalStatusComplete { 416 t.Fatalf("bad: %v", out) 417 } 418 if out.SnapshotIndex != w.snapshotIndex { 419 t.Fatalf("bad: %v", out) 420 } 421 } 422 423 func TestWorker_CreateEval(t *testing.T) { 424 t.Parallel() 425 s1 := testServer(t, func(c *Config) { 426 c.NumSchedulers = 0 427 c.EnabledSchedulers = []string{structs.JobTypeService} 428 }) 429 defer s1.Shutdown() 430 testutil.WaitForLeader(t, s1.RPC) 431 432 // Register node 433 node := mock.Node() 434 testRegisterNode(t, s1, node) 435 436 // Create the register request 437 eval1 := mock.Eval() 438 s1.evalBroker.Enqueue(eval1) 439 440 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 441 if err != nil { 442 t.Fatalf("err: %v", err) 443 } 444 if evalOut != eval1 { 445 t.Fatalf("Bad eval") 446 } 447 448 eval2 := mock.Eval() 449 eval2.PreviousEval = eval1.ID 450 451 // Attempt to create eval 452 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 453 err = w.CreateEval(eval2) 454 if err != nil { 455 t.Fatalf("err: %v", err) 456 } 457 458 ws := memdb.NewWatchSet() 459 out, err := s1.fsm.State().EvalByID(ws, eval2.ID) 460 if err != nil { 461 t.Fatalf("err: %v", err) 462 } 463 if out.PreviousEval != eval1.ID { 464 t.Fatalf("bad: %v", out) 465 } 466 if out.SnapshotIndex != w.snapshotIndex { 467 t.Fatalf("bad: %v", out) 468 } 469 } 470 471 func TestWorker_ReblockEval(t *testing.T) { 472 t.Parallel() 473 s1 := testServer(t, func(c *Config) { 474 c.NumSchedulers = 0 475 c.EnabledSchedulers = []string{structs.JobTypeService} 476 }) 477 defer s1.Shutdown() 478 testutil.WaitForLeader(t, s1.RPC) 479 480 // Create the blocked eval 481 eval1 := mock.Eval() 482 eval1.Status = structs.EvalStatusBlocked 483 eval1.QueuedAllocations = map[string]int{"cache": 100} 484 485 // Insert it into the state store 486 if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil { 487 t.Fatal(err) 488 } 489 490 // Create the job summary 491 js := mock.JobSummary(eval1.JobID) 492 tg := js.Summary["web"] 493 tg.Queued = 100 494 js.Summary["web"] = tg 495 if err := s1.fsm.State().UpsertJobSummary(1001, js); err != nil { 496 t.Fatal(err) 497 } 498 499 // Enqueue the eval and then dequeue 500 s1.evalBroker.Enqueue(eval1) 501 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 502 if err != nil { 503 t.Fatalf("err: %v", err) 504 } 505 if evalOut != eval1 { 506 t.Fatalf("Bad eval") 507 } 508 509 eval2 := evalOut.Copy() 510 eval2.QueuedAllocations = map[string]int{"web": 50} 511 512 // Attempt to reblock eval 513 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 514 err = w.ReblockEval(eval2) 515 if err != nil { 516 t.Fatalf("err: %v", err) 517 } 518 519 // Ack the eval 520 w.sendAck(evalOut.ID, token, true) 521 522 // Check that it is blocked 523 bStats := s1.blockedEvals.Stats() 524 if bStats.TotalBlocked+bStats.TotalEscaped != 1 { 525 t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats) 526 } 527 528 // Check that the eval was updated 529 ws := memdb.NewWatchSet() 530 eval, err := s1.fsm.State().EvalByID(ws, eval2.ID) 531 if err != nil { 532 t.Fatal(err) 533 } 534 if !reflect.DeepEqual(eval.QueuedAllocations, eval2.QueuedAllocations) { 535 t.Fatalf("expected: %#v, actual: %#v", eval2.QueuedAllocations, eval.QueuedAllocations) 536 } 537 538 // Check that the snapshot index was set properly by unblocking the eval and 539 // then dequeuing. 540 s1.blockedEvals.Unblock("foobar", 1000) 541 542 reblockedEval, _, err := s1.evalBroker.Dequeue([]string{eval1.Type}, 1*time.Second) 543 if err != nil { 544 t.Fatalf("err: %v", err) 545 } 546 if reblockedEval == nil { 547 t.Fatalf("Nil eval") 548 } 549 if reblockedEval.ID != eval1.ID { 550 t.Fatalf("Bad eval") 551 } 552 553 // Check that the SnapshotIndex is set 554 if reblockedEval.SnapshotIndex != w.snapshotIndex { 555 t.Fatalf("incorrect snapshot index; got %d; want %d", 556 reblockedEval.SnapshotIndex, w.snapshotIndex) 557 } 558 }