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