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