github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/job_endpoint_test.go (about) 1 package nomad 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/net-rpc-msgpackrpc" 9 "github.com/hashicorp/nomad/nomad/mock" 10 "github.com/hashicorp/nomad/nomad/structs" 11 "github.com/hashicorp/nomad/testutil" 12 ) 13 14 func TestJobEndpoint_Register(t *testing.T) { 15 s1 := testServer(t, func(c *Config) { 16 c.NumSchedulers = 0 // Prevent automatic dequeue 17 }) 18 defer s1.Shutdown() 19 codec := rpcClient(t, s1) 20 testutil.WaitForLeader(t, s1.RPC) 21 22 // Create the register request 23 job := mock.Job() 24 req := &structs.JobRegisterRequest{ 25 Job: job, 26 WriteRequest: structs.WriteRequest{Region: "global"}, 27 } 28 29 // Fetch the response 30 var resp structs.JobRegisterResponse 31 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 32 t.Fatalf("err: %v", err) 33 } 34 if resp.Index == 0 { 35 t.Fatalf("bad index: %d", resp.Index) 36 } 37 38 // Check for the node in the FSM 39 state := s1.fsm.State() 40 out, err := state.JobByID(job.ID) 41 if err != nil { 42 t.Fatalf("err: %v", err) 43 } 44 if out == nil { 45 t.Fatalf("expected job") 46 } 47 if out.CreateIndex != resp.JobModifyIndex { 48 t.Fatalf("index mis-match") 49 } 50 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 51 expectedServiceName := "web-frontend" 52 if serviceName != expectedServiceName { 53 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 54 } 55 56 // Lookup the evaluation 57 eval, err := state.EvalByID(resp.EvalID) 58 if err != nil { 59 t.Fatalf("err: %v", err) 60 } 61 if eval == nil { 62 t.Fatalf("expected eval") 63 } 64 if eval.CreateIndex != resp.EvalCreateIndex { 65 t.Fatalf("index mis-match") 66 } 67 68 if eval.Priority != job.Priority { 69 t.Fatalf("bad: %#v", eval) 70 } 71 if eval.Type != job.Type { 72 t.Fatalf("bad: %#v", eval) 73 } 74 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 75 t.Fatalf("bad: %#v", eval) 76 } 77 if eval.JobID != job.ID { 78 t.Fatalf("bad: %#v", eval) 79 } 80 if eval.JobModifyIndex != resp.JobModifyIndex { 81 t.Fatalf("bad: %#v", eval) 82 } 83 if eval.Status != structs.EvalStatusPending { 84 t.Fatalf("bad: %#v", eval) 85 } 86 } 87 88 func TestJobEndpoint_Register_Existing(t *testing.T) { 89 s1 := testServer(t, func(c *Config) { 90 c.NumSchedulers = 0 // Prevent automatic dequeue 91 }) 92 defer s1.Shutdown() 93 codec := rpcClient(t, s1) 94 testutil.WaitForLeader(t, s1.RPC) 95 96 // Create the register request 97 job := mock.Job() 98 req := &structs.JobRegisterRequest{ 99 Job: job, 100 WriteRequest: structs.WriteRequest{Region: "global"}, 101 } 102 103 // Fetch the response 104 var resp structs.JobRegisterResponse 105 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 106 t.Fatalf("err: %v", err) 107 } 108 if resp.Index == 0 { 109 t.Fatalf("bad index: %d", resp.Index) 110 } 111 112 // Update the job definition 113 job2 := mock.Job() 114 job2.Priority = 100 115 job2.ID = job.ID 116 req.Job = job2 117 118 // Attempt update 119 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 120 t.Fatalf("err: %v", err) 121 } 122 if resp.Index == 0 { 123 t.Fatalf("bad index: %d", resp.Index) 124 } 125 126 // Check for the node in the FSM 127 state := s1.fsm.State() 128 out, err := state.JobByID(job.ID) 129 if err != nil { 130 t.Fatalf("err: %v", err) 131 } 132 if out == nil { 133 t.Fatalf("expected job") 134 } 135 if out.ModifyIndex != resp.JobModifyIndex { 136 t.Fatalf("index mis-match") 137 } 138 if out.Priority != 100 { 139 t.Fatalf("expected update") 140 } 141 142 // Lookup the evaluation 143 eval, err := state.EvalByID(resp.EvalID) 144 if err != nil { 145 t.Fatalf("err: %v", err) 146 } 147 if eval == nil { 148 t.Fatalf("expected eval") 149 } 150 if eval.CreateIndex != resp.EvalCreateIndex { 151 t.Fatalf("index mis-match") 152 } 153 154 if eval.Priority != job2.Priority { 155 t.Fatalf("bad: %#v", eval) 156 } 157 if eval.Type != job2.Type { 158 t.Fatalf("bad: %#v", eval) 159 } 160 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 161 t.Fatalf("bad: %#v", eval) 162 } 163 if eval.JobID != job2.ID { 164 t.Fatalf("bad: %#v", eval) 165 } 166 if eval.JobModifyIndex != resp.JobModifyIndex { 167 t.Fatalf("bad: %#v", eval) 168 } 169 if eval.Status != structs.EvalStatusPending { 170 t.Fatalf("bad: %#v", eval) 171 } 172 } 173 174 func TestJobEndpoint_Register_Batch(t *testing.T) { 175 s1 := testServer(t, func(c *Config) { 176 c.NumSchedulers = 0 // Prevent automatic dequeue 177 }) 178 defer s1.Shutdown() 179 codec := rpcClient(t, s1) 180 testutil.WaitForLeader(t, s1.RPC) 181 182 // Create the register request 183 job := mock.Job() 184 job.Type = structs.JobTypeBatch 185 req := &structs.JobRegisterRequest{ 186 Job: job, 187 WriteRequest: structs.WriteRequest{Region: "global"}, 188 } 189 190 // Fetch the response 191 var resp structs.JobRegisterResponse 192 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 193 t.Fatalf("err: %v", err) 194 } 195 if resp.Index == 0 { 196 t.Fatalf("bad index: %d", resp.Index) 197 } 198 199 // Check for the node in the FSM 200 state := s1.fsm.State() 201 out, err := state.JobByID(job.ID) 202 if err != nil { 203 t.Fatalf("err: %v", err) 204 } 205 if out == nil { 206 t.Fatalf("expected job") 207 } 208 if !out.GC { 209 t.Fatal("expect batch job to be made garbage collectible") 210 } 211 } 212 213 func TestJobEndpoint_Register_GC_Set(t *testing.T) { 214 s1 := testServer(t, func(c *Config) { 215 c.NumSchedulers = 0 // Prevent automatic dequeue 216 }) 217 defer s1.Shutdown() 218 codec := rpcClient(t, s1) 219 testutil.WaitForLeader(t, s1.RPC) 220 221 // Create the register request 222 job := mock.Job() 223 job.GC = true 224 req := &structs.JobRegisterRequest{ 225 Job: job, 226 WriteRequest: structs.WriteRequest{Region: "global"}, 227 } 228 229 // Fetch the response 230 var resp structs.JobRegisterResponse 231 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err == nil { 232 t.Fatalf("expect err") 233 } 234 } 235 236 func TestJobEndpoint_Register_Periodic(t *testing.T) { 237 s1 := testServer(t, func(c *Config) { 238 c.NumSchedulers = 0 // Prevent automatic dequeue 239 }) 240 defer s1.Shutdown() 241 codec := rpcClient(t, s1) 242 testutil.WaitForLeader(t, s1.RPC) 243 244 // Create the register request for a periodic job. 245 job := mock.PeriodicJob() 246 req := &structs.JobRegisterRequest{ 247 Job: job, 248 WriteRequest: structs.WriteRequest{Region: "global"}, 249 } 250 251 // Fetch the response 252 var resp structs.JobRegisterResponse 253 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 254 t.Fatalf("err: %v", err) 255 } 256 if resp.JobModifyIndex == 0 { 257 t.Fatalf("bad index: %d", resp.Index) 258 } 259 260 // Check for the node in the FSM 261 state := s1.fsm.State() 262 out, err := state.JobByID(job.ID) 263 if err != nil { 264 t.Fatalf("err: %v", err) 265 } 266 if out == nil { 267 t.Fatalf("expected job") 268 } 269 if out.CreateIndex != resp.JobModifyIndex { 270 t.Fatalf("index mis-match") 271 } 272 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 273 expectedServiceName := "web-frontend" 274 if serviceName != expectedServiceName { 275 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 276 } 277 278 if resp.EvalID != "" { 279 t.Fatalf("Register created an eval for a periodic job") 280 } 281 } 282 283 func TestJobEndpoint_Evaluate(t *testing.T) { 284 s1 := testServer(t, func(c *Config) { 285 c.NumSchedulers = 0 // Prevent automatic dequeue 286 }) 287 defer s1.Shutdown() 288 codec := rpcClient(t, s1) 289 testutil.WaitForLeader(t, s1.RPC) 290 291 // Create the register request 292 job := mock.Job() 293 req := &structs.JobRegisterRequest{ 294 Job: job, 295 WriteRequest: structs.WriteRequest{Region: "global"}, 296 } 297 298 // Fetch the response 299 var resp structs.JobRegisterResponse 300 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 301 t.Fatalf("err: %v", err) 302 } 303 if resp.Index == 0 { 304 t.Fatalf("bad index: %d", resp.Index) 305 } 306 307 // Force a re-evaluation 308 reEval := &structs.JobEvaluateRequest{ 309 JobID: job.ID, 310 WriteRequest: structs.WriteRequest{Region: "global"}, 311 } 312 313 // Fetch the response 314 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil { 315 t.Fatalf("err: %v", err) 316 } 317 if resp.Index == 0 { 318 t.Fatalf("bad index: %d", resp.Index) 319 } 320 321 // Lookup the evaluation 322 state := s1.fsm.State() 323 eval, err := state.EvalByID(resp.EvalID) 324 if err != nil { 325 t.Fatalf("err: %v", err) 326 } 327 if eval == nil { 328 t.Fatalf("expected eval") 329 } 330 if eval.CreateIndex != resp.EvalCreateIndex { 331 t.Fatalf("index mis-match") 332 } 333 334 if eval.Priority != job.Priority { 335 t.Fatalf("bad: %#v", eval) 336 } 337 if eval.Type != job.Type { 338 t.Fatalf("bad: %#v", eval) 339 } 340 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 341 t.Fatalf("bad: %#v", eval) 342 } 343 if eval.JobID != job.ID { 344 t.Fatalf("bad: %#v", eval) 345 } 346 if eval.JobModifyIndex != resp.JobModifyIndex { 347 t.Fatalf("bad: %#v", eval) 348 } 349 if eval.Status != structs.EvalStatusPending { 350 t.Fatalf("bad: %#v", eval) 351 } 352 } 353 354 func TestJobEndpoint_Evaluate_Periodic(t *testing.T) { 355 s1 := testServer(t, func(c *Config) { 356 c.NumSchedulers = 0 // Prevent automatic dequeue 357 }) 358 defer s1.Shutdown() 359 codec := rpcClient(t, s1) 360 testutil.WaitForLeader(t, s1.RPC) 361 362 // Create the register request 363 job := mock.PeriodicJob() 364 req := &structs.JobRegisterRequest{ 365 Job: job, 366 WriteRequest: structs.WriteRequest{Region: "global"}, 367 } 368 369 // Fetch the response 370 var resp structs.JobRegisterResponse 371 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 372 t.Fatalf("err: %v", err) 373 } 374 if resp.JobModifyIndex == 0 { 375 t.Fatalf("bad index: %d", resp.Index) 376 } 377 378 // Force a re-evaluation 379 reEval := &structs.JobEvaluateRequest{ 380 JobID: job.ID, 381 WriteRequest: structs.WriteRequest{Region: "global"}, 382 } 383 384 // Fetch the response 385 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 386 t.Fatal("expect an err") 387 } 388 } 389 390 func TestJobEndpoint_Deregister(t *testing.T) { 391 s1 := testServer(t, func(c *Config) { 392 c.NumSchedulers = 0 // Prevent automatic dequeue 393 }) 394 defer s1.Shutdown() 395 codec := rpcClient(t, s1) 396 testutil.WaitForLeader(t, s1.RPC) 397 398 // Create the register request 399 job := mock.Job() 400 reg := &structs.JobRegisterRequest{ 401 Job: job, 402 WriteRequest: structs.WriteRequest{Region: "global"}, 403 } 404 405 // Fetch the response 406 var resp structs.JobRegisterResponse 407 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 408 t.Fatalf("err: %v", err) 409 } 410 411 // Deregister 412 dereg := &structs.JobDeregisterRequest{ 413 JobID: job.ID, 414 WriteRequest: structs.WriteRequest{Region: "global"}, 415 } 416 var resp2 structs.JobDeregisterResponse 417 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 418 t.Fatalf("err: %v", err) 419 } 420 if resp2.Index == 0 { 421 t.Fatalf("bad index: %d", resp2.Index) 422 } 423 424 // Check for the node in the FSM 425 state := s1.fsm.State() 426 out, err := state.JobByID(job.ID) 427 if err != nil { 428 t.Fatalf("err: %v", err) 429 } 430 if out != nil { 431 t.Fatalf("unexpected job") 432 } 433 434 // Lookup the evaluation 435 eval, err := state.EvalByID(resp2.EvalID) 436 if err != nil { 437 t.Fatalf("err: %v", err) 438 } 439 if eval == nil { 440 t.Fatalf("expected eval") 441 } 442 if eval.CreateIndex != resp2.EvalCreateIndex { 443 t.Fatalf("index mis-match") 444 } 445 446 if eval.Priority != structs.JobDefaultPriority { 447 t.Fatalf("bad: %#v", eval) 448 } 449 if eval.Type != structs.JobTypeService { 450 t.Fatalf("bad: %#v", eval) 451 } 452 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 453 t.Fatalf("bad: %#v", eval) 454 } 455 if eval.JobID != job.ID { 456 t.Fatalf("bad: %#v", eval) 457 } 458 if eval.JobModifyIndex != resp2.JobModifyIndex { 459 t.Fatalf("bad: %#v", eval) 460 } 461 if eval.Status != structs.EvalStatusPending { 462 t.Fatalf("bad: %#v", eval) 463 } 464 } 465 466 func TestJobEndpoint_Deregister_Periodic(t *testing.T) { 467 s1 := testServer(t, func(c *Config) { 468 c.NumSchedulers = 0 // Prevent automatic dequeue 469 }) 470 defer s1.Shutdown() 471 codec := rpcClient(t, s1) 472 testutil.WaitForLeader(t, s1.RPC) 473 474 // Create the register request 475 job := mock.PeriodicJob() 476 reg := &structs.JobRegisterRequest{ 477 Job: job, 478 WriteRequest: structs.WriteRequest{Region: "global"}, 479 } 480 481 // Fetch the response 482 var resp structs.JobRegisterResponse 483 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 484 t.Fatalf("err: %v", err) 485 } 486 487 // Deregister 488 dereg := &structs.JobDeregisterRequest{ 489 JobID: job.ID, 490 WriteRequest: structs.WriteRequest{Region: "global"}, 491 } 492 var resp2 structs.JobDeregisterResponse 493 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 494 t.Fatalf("err: %v", err) 495 } 496 if resp2.JobModifyIndex == 0 { 497 t.Fatalf("bad index: %d", resp2.Index) 498 } 499 500 // Check for the node in the FSM 501 state := s1.fsm.State() 502 out, err := state.JobByID(job.ID) 503 if err != nil { 504 t.Fatalf("err: %v", err) 505 } 506 if out != nil { 507 t.Fatalf("unexpected job") 508 } 509 510 if resp.EvalID != "" { 511 t.Fatalf("Deregister created an eval for a periodic job") 512 } 513 } 514 515 func TestJobEndpoint_GetJob(t *testing.T) { 516 s1 := testServer(t, nil) 517 defer s1.Shutdown() 518 codec := rpcClient(t, s1) 519 testutil.WaitForLeader(t, s1.RPC) 520 521 // Create the register request 522 job := mock.Job() 523 reg := &structs.JobRegisterRequest{ 524 Job: job, 525 WriteRequest: structs.WriteRequest{Region: "global"}, 526 } 527 528 // Fetch the response 529 var resp structs.JobRegisterResponse 530 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 531 t.Fatalf("err: %v", err) 532 } 533 job.CreateIndex = resp.JobModifyIndex 534 job.ModifyIndex = resp.JobModifyIndex 535 job.JobModifyIndex = resp.JobModifyIndex 536 537 // Lookup the job 538 get := &structs.JobSpecificRequest{ 539 JobID: job.ID, 540 QueryOptions: structs.QueryOptions{Region: "global"}, 541 } 542 var resp2 structs.SingleJobResponse 543 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 544 t.Fatalf("err: %v", err) 545 } 546 if resp2.Index != resp.JobModifyIndex { 547 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 548 } 549 550 // Make a copy of the origin job and change the service name so that we can 551 // do a deep equal with the response from the GET JOB Api 552 j := job 553 j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend" 554 for tgix, tg := range j.TaskGroups { 555 for tidx, t := range tg.Tasks { 556 for sidx, service := range t.Services { 557 for cidx, check := range service.Checks { 558 check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name 559 } 560 } 561 } 562 } 563 564 if !reflect.DeepEqual(j, resp2.Job) { 565 t.Fatalf("bad: %#v %#v", job, resp2.Job) 566 } 567 568 // Lookup non-existing job 569 get.JobID = "foobarbaz" 570 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 571 t.Fatalf("err: %v", err) 572 } 573 if resp2.Index != resp.JobModifyIndex { 574 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 575 } 576 if resp2.Job != nil { 577 t.Fatalf("unexpected job") 578 } 579 } 580 581 func TestJobEndpoint_GetJob_Blocking(t *testing.T) { 582 s1 := testServer(t, nil) 583 defer s1.Shutdown() 584 state := s1.fsm.State() 585 codec := rpcClient(t, s1) 586 testutil.WaitForLeader(t, s1.RPC) 587 588 // Create the jobs 589 job1 := mock.Job() 590 job2 := mock.Job() 591 592 // Upsert a job we are not interested in first. 593 time.AfterFunc(100*time.Millisecond, func() { 594 if err := state.UpsertJob(100, job1); err != nil { 595 t.Fatalf("err: %v", err) 596 } 597 }) 598 599 // Upsert another job later which should trigger the watch. 600 time.AfterFunc(200*time.Millisecond, func() { 601 if err := state.UpsertJob(200, job2); err != nil { 602 t.Fatalf("err: %v", err) 603 } 604 }) 605 606 req := &structs.JobSpecificRequest{ 607 JobID: job2.ID, 608 QueryOptions: structs.QueryOptions{ 609 Region: "global", 610 MinQueryIndex: 50, 611 }, 612 } 613 start := time.Now() 614 var resp structs.SingleJobResponse 615 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil { 616 t.Fatalf("err: %v", err) 617 } 618 619 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 620 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 621 } 622 if resp.Index != 200 { 623 t.Fatalf("Bad index: %d %d", resp.Index, 200) 624 } 625 if resp.Job == nil || resp.Job.ID != job2.ID { 626 t.Fatalf("bad: %#v", resp.Job) 627 } 628 629 // Job delete fires watches 630 time.AfterFunc(100*time.Millisecond, func() { 631 if err := state.DeleteJob(300, job2.ID); err != nil { 632 t.Fatalf("err: %v", err) 633 } 634 }) 635 636 req.QueryOptions.MinQueryIndex = 250 637 start = time.Now() 638 639 var resp2 structs.SingleJobResponse 640 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil { 641 t.Fatalf("err: %v", err) 642 } 643 644 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 645 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 646 } 647 if resp2.Index != 300 { 648 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 649 } 650 if resp2.Job != nil { 651 t.Fatalf("bad: %#v", resp2.Job) 652 } 653 } 654 655 func TestJobEndpoint_ListJobs(t *testing.T) { 656 s1 := testServer(t, nil) 657 defer s1.Shutdown() 658 codec := rpcClient(t, s1) 659 testutil.WaitForLeader(t, s1.RPC) 660 661 // Create the register request 662 job := mock.Job() 663 state := s1.fsm.State() 664 err := state.UpsertJob(1000, job) 665 if err != nil { 666 t.Fatalf("err: %v", err) 667 } 668 669 // Lookup the jobs 670 get := &structs.JobListRequest{ 671 QueryOptions: structs.QueryOptions{Region: "global"}, 672 } 673 var resp2 structs.JobListResponse 674 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil { 675 t.Fatalf("err: %v", err) 676 } 677 if resp2.Index != 1000 { 678 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 679 } 680 681 if len(resp2.Jobs) != 1 { 682 t.Fatalf("bad: %#v", resp2.Jobs) 683 } 684 if resp2.Jobs[0].ID != job.ID { 685 t.Fatalf("bad: %#v", resp2.Jobs[0]) 686 } 687 688 // Lookup the jobs by prefix 689 get = &structs.JobListRequest{ 690 QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]}, 691 } 692 var resp3 structs.JobListResponse 693 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil { 694 t.Fatalf("err: %v", err) 695 } 696 if resp3.Index != 1000 { 697 t.Fatalf("Bad index: %d %d", resp3.Index, 1000) 698 } 699 700 if len(resp3.Jobs) != 1 { 701 t.Fatalf("bad: %#v", resp3.Jobs) 702 } 703 if resp3.Jobs[0].ID != job.ID { 704 t.Fatalf("bad: %#v", resp3.Jobs[0]) 705 } 706 } 707 708 func TestJobEndpoint_ListJobs_Blocking(t *testing.T) { 709 s1 := testServer(t, nil) 710 defer s1.Shutdown() 711 state := s1.fsm.State() 712 codec := rpcClient(t, s1) 713 testutil.WaitForLeader(t, s1.RPC) 714 715 // Create the job 716 job := mock.Job() 717 718 // Upsert job triggers watches 719 time.AfterFunc(100*time.Millisecond, func() { 720 if err := state.UpsertJob(100, job); err != nil { 721 t.Fatalf("err: %v", err) 722 } 723 }) 724 725 req := &structs.JobListRequest{ 726 QueryOptions: structs.QueryOptions{ 727 Region: "global", 728 MinQueryIndex: 50, 729 }, 730 } 731 start := time.Now() 732 var resp structs.JobListResponse 733 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil { 734 t.Fatalf("err: %v", err) 735 } 736 737 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 738 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 739 } 740 if resp.Index != 100 { 741 t.Fatalf("Bad index: %d %d", resp.Index, 100) 742 } 743 if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID { 744 t.Fatalf("bad: %#v", resp.Jobs) 745 } 746 747 // Job deletion triggers watches 748 time.AfterFunc(100*time.Millisecond, func() { 749 if err := state.DeleteJob(200, job.ID); err != nil { 750 t.Fatalf("err: %v", err) 751 } 752 }) 753 754 req.MinQueryIndex = 150 755 start = time.Now() 756 var resp2 structs.JobListResponse 757 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil { 758 t.Fatalf("err: %v", err) 759 } 760 761 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 762 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 763 } 764 if resp2.Index != 200 { 765 t.Fatalf("Bad index: %d %d", resp2.Index, 200) 766 } 767 if len(resp2.Jobs) != 0 { 768 t.Fatalf("bad: %#v", resp2.Jobs) 769 } 770 } 771 772 func TestJobEndpoint_Allocations(t *testing.T) { 773 s1 := testServer(t, nil) 774 defer s1.Shutdown() 775 codec := rpcClient(t, s1) 776 testutil.WaitForLeader(t, s1.RPC) 777 778 // Create the register request 779 alloc1 := mock.Alloc() 780 alloc2 := mock.Alloc() 781 alloc2.JobID = alloc1.JobID 782 state := s1.fsm.State() 783 err := state.UpsertAllocs(1000, 784 []*structs.Allocation{alloc1, alloc2}) 785 if err != nil { 786 t.Fatalf("err: %v", err) 787 } 788 789 // Lookup the jobs 790 get := &structs.JobSpecificRequest{ 791 JobID: alloc1.JobID, 792 QueryOptions: structs.QueryOptions{Region: "global"}, 793 } 794 var resp2 structs.JobAllocationsResponse 795 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil { 796 t.Fatalf("err: %v", err) 797 } 798 if resp2.Index != 1000 { 799 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 800 } 801 802 if len(resp2.Allocations) != 2 { 803 t.Fatalf("bad: %#v", resp2.Allocations) 804 } 805 } 806 807 func TestJobEndpoint_Allocations_Blocking(t *testing.T) { 808 s1 := testServer(t, nil) 809 defer s1.Shutdown() 810 codec := rpcClient(t, s1) 811 testutil.WaitForLeader(t, s1.RPC) 812 813 // Create the register request 814 alloc1 := mock.Alloc() 815 alloc2 := mock.Alloc() 816 alloc2.JobID = "job1" 817 state := s1.fsm.State() 818 819 // First upsert an unrelated alloc 820 time.AfterFunc(100*time.Millisecond, func() { 821 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 822 if err != nil { 823 t.Fatalf("err: %v", err) 824 } 825 }) 826 827 // Upsert an alloc for the job we are interested in later 828 time.AfterFunc(200*time.Millisecond, func() { 829 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 830 if err != nil { 831 t.Fatalf("err: %v", err) 832 } 833 }) 834 835 // Lookup the jobs 836 get := &structs.JobSpecificRequest{ 837 JobID: "job1", 838 QueryOptions: structs.QueryOptions{ 839 Region: "global", 840 MinQueryIndex: 50, 841 }, 842 } 843 var resp structs.JobAllocationsResponse 844 start := time.Now() 845 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil { 846 t.Fatalf("err: %v", err) 847 } 848 849 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 850 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 851 } 852 if resp.Index != 200 { 853 t.Fatalf("Bad index: %d %d", resp.Index, 200) 854 } 855 if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" { 856 t.Fatalf("bad: %#v", resp.Allocations) 857 } 858 } 859 860 func TestJobEndpoint_Evaluations(t *testing.T) { 861 s1 := testServer(t, nil) 862 defer s1.Shutdown() 863 codec := rpcClient(t, s1) 864 testutil.WaitForLeader(t, s1.RPC) 865 866 // Create the register request 867 eval1 := mock.Eval() 868 eval2 := mock.Eval() 869 eval2.JobID = eval1.JobID 870 state := s1.fsm.State() 871 err := state.UpsertEvals(1000, 872 []*structs.Evaluation{eval1, eval2}) 873 if err != nil { 874 t.Fatalf("err: %v", err) 875 } 876 877 // Lookup the jobs 878 get := &structs.JobSpecificRequest{ 879 JobID: eval1.JobID, 880 QueryOptions: structs.QueryOptions{Region: "global"}, 881 } 882 var resp2 structs.JobEvaluationsResponse 883 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil { 884 t.Fatalf("err: %v", err) 885 } 886 if resp2.Index != 1000 { 887 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 888 } 889 890 if len(resp2.Evaluations) != 2 { 891 t.Fatalf("bad: %#v", resp2.Evaluations) 892 } 893 }