github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/nomad/job_endpoint_test.go (about) 1 package nomad 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/net-rpc-msgpackrpc" 11 "github.com/hashicorp/nomad/nomad/mock" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/testutil" 14 ) 15 16 func TestJobEndpoint_Register(t *testing.T) { 17 s1 := testServer(t, func(c *Config) { 18 c.NumSchedulers = 0 // Prevent automatic dequeue 19 }) 20 defer s1.Shutdown() 21 codec := rpcClient(t, s1) 22 testutil.WaitForLeader(t, s1.RPC) 23 24 // Create the register request 25 job := mock.Job() 26 req := &structs.JobRegisterRequest{ 27 Job: job, 28 WriteRequest: structs.WriteRequest{Region: "global"}, 29 } 30 31 // Fetch the response 32 var resp structs.JobRegisterResponse 33 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 34 t.Fatalf("err: %v", err) 35 } 36 if resp.Index == 0 { 37 t.Fatalf("bad index: %d", resp.Index) 38 } 39 40 // Check for the node in the FSM 41 state := s1.fsm.State() 42 out, err := state.JobByID(job.ID) 43 if err != nil { 44 t.Fatalf("err: %v", err) 45 } 46 if out == nil { 47 t.Fatalf("expected job") 48 } 49 if out.CreateIndex != resp.JobModifyIndex { 50 t.Fatalf("index mis-match") 51 } 52 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 53 expectedServiceName := "web-frontend" 54 if serviceName != expectedServiceName { 55 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 56 } 57 58 // Lookup the evaluation 59 eval, err := state.EvalByID(resp.EvalID) 60 if err != nil { 61 t.Fatalf("err: %v", err) 62 } 63 if eval == nil { 64 t.Fatalf("expected eval") 65 } 66 if eval.CreateIndex != resp.EvalCreateIndex { 67 t.Fatalf("index mis-match") 68 } 69 70 if eval.Priority != job.Priority { 71 t.Fatalf("bad: %#v", eval) 72 } 73 if eval.Type != job.Type { 74 t.Fatalf("bad: %#v", eval) 75 } 76 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 77 t.Fatalf("bad: %#v", eval) 78 } 79 if eval.JobID != job.ID { 80 t.Fatalf("bad: %#v", eval) 81 } 82 if eval.JobModifyIndex != resp.JobModifyIndex { 83 t.Fatalf("bad: %#v", eval) 84 } 85 if eval.Status != structs.EvalStatusPending { 86 t.Fatalf("bad: %#v", eval) 87 } 88 } 89 90 func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) { 91 s1 := testServer(t, func(c *Config) { 92 c.NumSchedulers = 0 // Prevent automatic dequeue 93 }) 94 defer s1.Shutdown() 95 codec := rpcClient(t, s1) 96 testutil.WaitForLeader(t, s1.RPC) 97 98 // Create the register request with a job containing an invalid driver 99 // config 100 job := mock.Job() 101 job.TaskGroups[0].Tasks[0].Config["foo"] = 1 102 req := &structs.JobRegisterRequest{ 103 Job: job, 104 WriteRequest: structs.WriteRequest{Region: "global"}, 105 } 106 107 // Fetch the response 108 var resp structs.JobRegisterResponse 109 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 110 if err == nil { 111 t.Fatalf("expected a validation error") 112 } 113 114 if !strings.Contains(err.Error(), "-> config:") { 115 t.Fatalf("expected a driver config validation error but got: %v", err) 116 } 117 } 118 119 func TestJobEndpoint_Register_Existing(t *testing.T) { 120 s1 := testServer(t, func(c *Config) { 121 c.NumSchedulers = 0 // Prevent automatic dequeue 122 }) 123 defer s1.Shutdown() 124 codec := rpcClient(t, s1) 125 testutil.WaitForLeader(t, s1.RPC) 126 127 // Create the register request 128 job := mock.Job() 129 req := &structs.JobRegisterRequest{ 130 Job: job, 131 WriteRequest: structs.WriteRequest{Region: "global"}, 132 } 133 134 // Fetch the response 135 var resp structs.JobRegisterResponse 136 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 137 t.Fatalf("err: %v", err) 138 } 139 if resp.Index == 0 { 140 t.Fatalf("bad index: %d", resp.Index) 141 } 142 143 // Update the job definition 144 job2 := mock.Job() 145 job2.Priority = 100 146 job2.ID = job.ID 147 req.Job = job2 148 149 // Attempt update 150 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 151 t.Fatalf("err: %v", err) 152 } 153 if resp.Index == 0 { 154 t.Fatalf("bad index: %d", resp.Index) 155 } 156 157 // Check for the node in the FSM 158 state := s1.fsm.State() 159 out, err := state.JobByID(job.ID) 160 if err != nil { 161 t.Fatalf("err: %v", err) 162 } 163 if out == nil { 164 t.Fatalf("expected job") 165 } 166 if out.ModifyIndex != resp.JobModifyIndex { 167 t.Fatalf("index mis-match") 168 } 169 if out.Priority != 100 { 170 t.Fatalf("expected update") 171 } 172 173 // Lookup the evaluation 174 eval, err := state.EvalByID(resp.EvalID) 175 if err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 if eval == nil { 179 t.Fatalf("expected eval") 180 } 181 if eval.CreateIndex != resp.EvalCreateIndex { 182 t.Fatalf("index mis-match") 183 } 184 185 if eval.Priority != job2.Priority { 186 t.Fatalf("bad: %#v", eval) 187 } 188 if eval.Type != job2.Type { 189 t.Fatalf("bad: %#v", eval) 190 } 191 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 192 t.Fatalf("bad: %#v", eval) 193 } 194 if eval.JobID != job2.ID { 195 t.Fatalf("bad: %#v", eval) 196 } 197 if eval.JobModifyIndex != resp.JobModifyIndex { 198 t.Fatalf("bad: %#v", eval) 199 } 200 if eval.Status != structs.EvalStatusPending { 201 t.Fatalf("bad: %#v", eval) 202 } 203 } 204 205 func TestJobEndpoint_Register_Periodic(t *testing.T) { 206 s1 := testServer(t, func(c *Config) { 207 c.NumSchedulers = 0 // Prevent automatic dequeue 208 }) 209 defer s1.Shutdown() 210 codec := rpcClient(t, s1) 211 testutil.WaitForLeader(t, s1.RPC) 212 213 // Create the register request for a periodic job. 214 job := mock.PeriodicJob() 215 req := &structs.JobRegisterRequest{ 216 Job: job, 217 WriteRequest: structs.WriteRequest{Region: "global"}, 218 } 219 220 // Fetch the response 221 var resp structs.JobRegisterResponse 222 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 223 t.Fatalf("err: %v", err) 224 } 225 if resp.JobModifyIndex == 0 { 226 t.Fatalf("bad index: %d", resp.Index) 227 } 228 229 // Check for the node in the FSM 230 state := s1.fsm.State() 231 out, err := state.JobByID(job.ID) 232 if err != nil { 233 t.Fatalf("err: %v", err) 234 } 235 if out == nil { 236 t.Fatalf("expected job") 237 } 238 if out.CreateIndex != resp.JobModifyIndex { 239 t.Fatalf("index mis-match") 240 } 241 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 242 expectedServiceName := "web-frontend" 243 if serviceName != expectedServiceName { 244 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 245 } 246 247 if resp.EvalID != "" { 248 t.Fatalf("Register created an eval for a periodic job") 249 } 250 } 251 252 func TestJobEndpoint_Register_EnforceIndex(t *testing.T) { 253 s1 := testServer(t, func(c *Config) { 254 c.NumSchedulers = 0 // Prevent automatic dequeue 255 }) 256 defer s1.Shutdown() 257 codec := rpcClient(t, s1) 258 testutil.WaitForLeader(t, s1.RPC) 259 260 // Create the register request and enforcing an incorrect index 261 job := mock.Job() 262 req := &structs.JobRegisterRequest{ 263 Job: job, 264 EnforceIndex: true, 265 JobModifyIndex: 100, // Not registered yet so not possible 266 WriteRequest: structs.WriteRequest{Region: "global"}, 267 } 268 269 // Fetch the response 270 var resp structs.JobRegisterResponse 271 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 272 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 273 t.Fatalf("expected enforcement error") 274 } 275 276 // Create the register request and enforcing it is new 277 req = &structs.JobRegisterRequest{ 278 Job: job, 279 EnforceIndex: true, 280 JobModifyIndex: 0, 281 WriteRequest: structs.WriteRequest{Region: "global"}, 282 } 283 284 // Fetch the response 285 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 286 t.Fatalf("err: %v", err) 287 } 288 if resp.Index == 0 { 289 t.Fatalf("bad index: %d", resp.Index) 290 } 291 292 curIndex := resp.JobModifyIndex 293 294 // Check for the node in the FSM 295 state := s1.fsm.State() 296 out, err := state.JobByID(job.ID) 297 if err != nil { 298 t.Fatalf("err: %v", err) 299 } 300 if out == nil { 301 t.Fatalf("expected job") 302 } 303 if out.CreateIndex != resp.JobModifyIndex { 304 t.Fatalf("index mis-match") 305 } 306 307 // Reregister request and enforcing it be a new job 308 req = &structs.JobRegisterRequest{ 309 Job: job, 310 EnforceIndex: true, 311 JobModifyIndex: 0, 312 WriteRequest: structs.WriteRequest{Region: "global"}, 313 } 314 315 // Fetch the response 316 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 317 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 318 t.Fatalf("expected enforcement error") 319 } 320 321 // Reregister request and enforcing it be at an incorrect index 322 req = &structs.JobRegisterRequest{ 323 Job: job, 324 EnforceIndex: true, 325 JobModifyIndex: curIndex - 1, 326 WriteRequest: structs.WriteRequest{Region: "global"}, 327 } 328 329 // Fetch the response 330 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 331 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 332 t.Fatalf("expected enforcement error") 333 } 334 335 // Reregister request and enforcing it be at the correct index 336 job.Priority = job.Priority + 1 337 req = &structs.JobRegisterRequest{ 338 Job: job, 339 EnforceIndex: true, 340 JobModifyIndex: curIndex, 341 WriteRequest: structs.WriteRequest{Region: "global"}, 342 } 343 344 // Fetch the response 345 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 346 t.Fatalf("err: %v", err) 347 } 348 if resp.Index == 0 { 349 t.Fatalf("bad index: %d", resp.Index) 350 } 351 352 out, err = state.JobByID(job.ID) 353 if err != nil { 354 t.Fatalf("err: %v", err) 355 } 356 if out == nil { 357 t.Fatalf("expected job") 358 } 359 if out.Priority != job.Priority { 360 t.Fatalf("priority mis-match") 361 } 362 } 363 364 func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) { 365 s1 := testServer(t, func(c *Config) { 366 c.NumSchedulers = 0 // Prevent automatic dequeue 367 f := false 368 c.VaultConfig.Enabled = &f 369 }) 370 defer s1.Shutdown() 371 codec := rpcClient(t, s1) 372 testutil.WaitForLeader(t, s1.RPC) 373 374 // Create the register request with a job asking for a vault policy 375 job := mock.Job() 376 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 377 Policies: []string{"foo"}, 378 ChangeMode: structs.VaultChangeModeRestart, 379 } 380 req := &structs.JobRegisterRequest{ 381 Job: job, 382 WriteRequest: structs.WriteRequest{Region: "global"}, 383 } 384 385 // Fetch the response 386 var resp structs.JobRegisterResponse 387 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 388 if err == nil || !strings.Contains(err.Error(), "Vault not enabled") { 389 t.Fatalf("expected Vault not enabled error: %v", err) 390 } 391 } 392 393 func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) { 394 s1 := testServer(t, func(c *Config) { 395 c.NumSchedulers = 0 // Prevent automatic dequeue 396 }) 397 defer s1.Shutdown() 398 codec := rpcClient(t, s1) 399 testutil.WaitForLeader(t, s1.RPC) 400 401 // Enable vault and allow authenticated 402 tr := true 403 s1.config.VaultConfig.Enabled = &tr 404 s1.config.VaultConfig.AllowUnauthenticated = &tr 405 406 // Replace the Vault Client on the server 407 s1.vault = &TestVaultClient{} 408 409 // Create the register request with a job asking for a vault policy 410 job := mock.Job() 411 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 412 Policies: []string{"foo"}, 413 ChangeMode: structs.VaultChangeModeRestart, 414 } 415 req := &structs.JobRegisterRequest{ 416 Job: job, 417 WriteRequest: structs.WriteRequest{Region: "global"}, 418 } 419 420 // Fetch the response 421 var resp structs.JobRegisterResponse 422 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 423 if err != nil { 424 t.Fatalf("bad: %v", err) 425 } 426 427 // Check for the job in the FSM 428 state := s1.fsm.State() 429 out, err := state.JobByID(job.ID) 430 if err != nil { 431 t.Fatalf("err: %v", err) 432 } 433 if out == nil { 434 t.Fatalf("expected job") 435 } 436 if out.CreateIndex != resp.JobModifyIndex { 437 t.Fatalf("index mis-match") 438 } 439 } 440 441 func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) { 442 s1 := testServer(t, func(c *Config) { 443 c.NumSchedulers = 0 // Prevent automatic dequeue 444 }) 445 defer s1.Shutdown() 446 codec := rpcClient(t, s1) 447 testutil.WaitForLeader(t, s1.RPC) 448 449 // Enable vault 450 tr, f := true, false 451 s1.config.VaultConfig.Enabled = &tr 452 s1.config.VaultConfig.AllowUnauthenticated = &f 453 454 // Replace the Vault Client on the server 455 s1.vault = &TestVaultClient{} 456 457 // Create the register request with a job asking for a vault policy but 458 // don't send a Vault token 459 job := mock.Job() 460 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 461 Policies: []string{"foo"}, 462 ChangeMode: structs.VaultChangeModeRestart, 463 } 464 req := &structs.JobRegisterRequest{ 465 Job: job, 466 WriteRequest: structs.WriteRequest{Region: "global"}, 467 } 468 469 // Fetch the response 470 var resp structs.JobRegisterResponse 471 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 472 if err == nil || !strings.Contains(err.Error(), "missing Vault Token") { 473 t.Fatalf("expected Vault not enabled error: %v", err) 474 } 475 } 476 477 func TestJobEndpoint_Register_Vault_Policies(t *testing.T) { 478 s1 := testServer(t, func(c *Config) { 479 c.NumSchedulers = 0 // Prevent automatic dequeue 480 }) 481 defer s1.Shutdown() 482 codec := rpcClient(t, s1) 483 testutil.WaitForLeader(t, s1.RPC) 484 485 // Enable vault 486 tr, f := true, false 487 s1.config.VaultConfig.Enabled = &tr 488 s1.config.VaultConfig.AllowUnauthenticated = &f 489 490 // Replace the Vault Client on the server 491 tvc := &TestVaultClient{} 492 s1.vault = tvc 493 494 // Add three tokens: one that allows the requesting policy, one that does 495 // not and one that returns an error 496 policy := "foo" 497 498 badToken := structs.GenerateUUID() 499 badPolicies := []string{"a", "b", "c"} 500 tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies) 501 502 goodToken := structs.GenerateUUID() 503 goodPolicies := []string{"foo", "bar", "baz"} 504 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 505 506 rootToken := structs.GenerateUUID() 507 rootPolicies := []string{"root"} 508 tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies) 509 510 errToken := structs.GenerateUUID() 511 expectedErr := fmt.Errorf("return errors from vault") 512 tvc.SetLookupTokenError(errToken, expectedErr) 513 514 // Create the register request with a job asking for a vault policy but 515 // send the bad Vault token 516 job := mock.Job() 517 job.VaultToken = badToken 518 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 519 Policies: []string{policy}, 520 ChangeMode: structs.VaultChangeModeRestart, 521 } 522 req := &structs.JobRegisterRequest{ 523 Job: job, 524 WriteRequest: structs.WriteRequest{Region: "global"}, 525 } 526 527 // Fetch the response 528 var resp structs.JobRegisterResponse 529 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 530 if err == nil || !strings.Contains(err.Error(), 531 "doesn't allow access to the following policies: "+policy) { 532 t.Fatalf("expected permission denied error: %v", err) 533 } 534 535 // Use the err token 536 job.VaultToken = errToken 537 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 538 if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) { 539 t.Fatalf("expected permission denied error: %v", err) 540 } 541 542 // Use the good token 543 job.VaultToken = goodToken 544 545 // Fetch the response 546 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 547 t.Fatalf("bad: %v", err) 548 } 549 550 // Check for the job in the FSM 551 state := s1.fsm.State() 552 out, err := state.JobByID(job.ID) 553 if err != nil { 554 t.Fatalf("err: %v", err) 555 } 556 if out == nil { 557 t.Fatalf("expected job") 558 } 559 if out.CreateIndex != resp.JobModifyIndex { 560 t.Fatalf("index mis-match") 561 } 562 if out.VaultToken != "" { 563 t.Fatalf("vault token not cleared") 564 } 565 566 // Check that an implicit constraint was created 567 constraints := out.TaskGroups[0].Constraints 568 if l := len(constraints); l != 1 { 569 t.Fatalf("Unexpected number of tests: %v", l) 570 } 571 572 if !constraints[0].Equal(vaultConstraint) { 573 t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint) 574 } 575 576 // Create the register request with another job asking for a vault policy but 577 // send the root Vault token 578 job2 := mock.Job() 579 job2.VaultToken = rootToken 580 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 581 Policies: []string{policy}, 582 ChangeMode: structs.VaultChangeModeRestart, 583 } 584 req = &structs.JobRegisterRequest{ 585 Job: job2, 586 WriteRequest: structs.WriteRequest{Region: "global"}, 587 } 588 589 // Fetch the response 590 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 591 t.Fatalf("bad: %v", err) 592 } 593 594 // Check for the job in the FSM 595 out, err = state.JobByID(job2.ID) 596 if err != nil { 597 t.Fatalf("err: %v", err) 598 } 599 if out == nil { 600 t.Fatalf("expected job") 601 } 602 if out.CreateIndex != resp.JobModifyIndex { 603 t.Fatalf("index mis-match") 604 } 605 if out.VaultToken != "" { 606 t.Fatalf("vault token not cleared") 607 } 608 } 609 610 func TestJobEndpoint_Evaluate(t *testing.T) { 611 s1 := testServer(t, func(c *Config) { 612 c.NumSchedulers = 0 // Prevent automatic dequeue 613 }) 614 defer s1.Shutdown() 615 codec := rpcClient(t, s1) 616 testutil.WaitForLeader(t, s1.RPC) 617 618 // Create the register request 619 job := mock.Job() 620 req := &structs.JobRegisterRequest{ 621 Job: job, 622 WriteRequest: structs.WriteRequest{Region: "global"}, 623 } 624 625 // Fetch the response 626 var resp structs.JobRegisterResponse 627 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 628 t.Fatalf("err: %v", err) 629 } 630 if resp.Index == 0 { 631 t.Fatalf("bad index: %d", resp.Index) 632 } 633 634 // Force a re-evaluation 635 reEval := &structs.JobEvaluateRequest{ 636 JobID: job.ID, 637 WriteRequest: structs.WriteRequest{Region: "global"}, 638 } 639 640 // Fetch the response 641 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil { 642 t.Fatalf("err: %v", err) 643 } 644 if resp.Index == 0 { 645 t.Fatalf("bad index: %d", resp.Index) 646 } 647 648 // Lookup the evaluation 649 state := s1.fsm.State() 650 eval, err := state.EvalByID(resp.EvalID) 651 if err != nil { 652 t.Fatalf("err: %v", err) 653 } 654 if eval == nil { 655 t.Fatalf("expected eval") 656 } 657 if eval.CreateIndex != resp.EvalCreateIndex { 658 t.Fatalf("index mis-match") 659 } 660 661 if eval.Priority != job.Priority { 662 t.Fatalf("bad: %#v", eval) 663 } 664 if eval.Type != job.Type { 665 t.Fatalf("bad: %#v", eval) 666 } 667 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 668 t.Fatalf("bad: %#v", eval) 669 } 670 if eval.JobID != job.ID { 671 t.Fatalf("bad: %#v", eval) 672 } 673 if eval.JobModifyIndex != resp.JobModifyIndex { 674 t.Fatalf("bad: %#v", eval) 675 } 676 if eval.Status != structs.EvalStatusPending { 677 t.Fatalf("bad: %#v", eval) 678 } 679 } 680 681 func TestJobEndpoint_Evaluate_Periodic(t *testing.T) { 682 s1 := testServer(t, func(c *Config) { 683 c.NumSchedulers = 0 // Prevent automatic dequeue 684 }) 685 defer s1.Shutdown() 686 codec := rpcClient(t, s1) 687 testutil.WaitForLeader(t, s1.RPC) 688 689 // Create the register request 690 job := mock.PeriodicJob() 691 req := &structs.JobRegisterRequest{ 692 Job: job, 693 WriteRequest: structs.WriteRequest{Region: "global"}, 694 } 695 696 // Fetch the response 697 var resp structs.JobRegisterResponse 698 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 699 t.Fatalf("err: %v", err) 700 } 701 if resp.JobModifyIndex == 0 { 702 t.Fatalf("bad index: %d", resp.Index) 703 } 704 705 // Force a re-evaluation 706 reEval := &structs.JobEvaluateRequest{ 707 JobID: job.ID, 708 WriteRequest: structs.WriteRequest{Region: "global"}, 709 } 710 711 // Fetch the response 712 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 713 t.Fatal("expect an err") 714 } 715 } 716 717 func TestJobEndpoint_Deregister(t *testing.T) { 718 s1 := testServer(t, func(c *Config) { 719 c.NumSchedulers = 0 // Prevent automatic dequeue 720 }) 721 defer s1.Shutdown() 722 codec := rpcClient(t, s1) 723 testutil.WaitForLeader(t, s1.RPC) 724 725 // Create the register request 726 job := mock.Job() 727 reg := &structs.JobRegisterRequest{ 728 Job: job, 729 WriteRequest: structs.WriteRequest{Region: "global"}, 730 } 731 732 // Fetch the response 733 var resp structs.JobRegisterResponse 734 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 735 t.Fatalf("err: %v", err) 736 } 737 738 // Deregister 739 dereg := &structs.JobDeregisterRequest{ 740 JobID: job.ID, 741 WriteRequest: structs.WriteRequest{Region: "global"}, 742 } 743 var resp2 structs.JobDeregisterResponse 744 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 745 t.Fatalf("err: %v", err) 746 } 747 if resp2.Index == 0 { 748 t.Fatalf("bad index: %d", resp2.Index) 749 } 750 751 // Check for the node in the FSM 752 state := s1.fsm.State() 753 out, err := state.JobByID(job.ID) 754 if err != nil { 755 t.Fatalf("err: %v", err) 756 } 757 if out != nil { 758 t.Fatalf("unexpected job") 759 } 760 761 // Lookup the evaluation 762 eval, err := state.EvalByID(resp2.EvalID) 763 if err != nil { 764 t.Fatalf("err: %v", err) 765 } 766 if eval == nil { 767 t.Fatalf("expected eval") 768 } 769 if eval.CreateIndex != resp2.EvalCreateIndex { 770 t.Fatalf("index mis-match") 771 } 772 773 if eval.Priority != structs.JobDefaultPriority { 774 t.Fatalf("bad: %#v", eval) 775 } 776 if eval.Type != structs.JobTypeService { 777 t.Fatalf("bad: %#v", eval) 778 } 779 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 780 t.Fatalf("bad: %#v", eval) 781 } 782 if eval.JobID != job.ID { 783 t.Fatalf("bad: %#v", eval) 784 } 785 if eval.JobModifyIndex != resp2.JobModifyIndex { 786 t.Fatalf("bad: %#v", eval) 787 } 788 if eval.Status != structs.EvalStatusPending { 789 t.Fatalf("bad: %#v", eval) 790 } 791 } 792 793 func TestJobEndpoint_Deregister_NonExistent(t *testing.T) { 794 s1 := testServer(t, func(c *Config) { 795 c.NumSchedulers = 0 // Prevent automatic dequeue 796 }) 797 defer s1.Shutdown() 798 codec := rpcClient(t, s1) 799 testutil.WaitForLeader(t, s1.RPC) 800 801 // Deregister 802 jobID := "foo" 803 dereg := &structs.JobDeregisterRequest{ 804 JobID: jobID, 805 WriteRequest: structs.WriteRequest{Region: "global"}, 806 } 807 var resp2 structs.JobDeregisterResponse 808 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 809 t.Fatalf("err: %v", err) 810 } 811 if resp2.JobModifyIndex == 0 { 812 t.Fatalf("bad index: %d", resp2.Index) 813 } 814 815 // Lookup the evaluation 816 state := s1.fsm.State() 817 eval, err := state.EvalByID(resp2.EvalID) 818 if err != nil { 819 t.Fatalf("err: %v", err) 820 } 821 if eval == nil { 822 t.Fatalf("expected eval") 823 } 824 if eval.CreateIndex != resp2.EvalCreateIndex { 825 t.Fatalf("index mis-match") 826 } 827 828 if eval.Priority != structs.JobDefaultPriority { 829 t.Fatalf("bad: %#v", eval) 830 } 831 if eval.Type != structs.JobTypeService { 832 t.Fatalf("bad: %#v", eval) 833 } 834 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 835 t.Fatalf("bad: %#v", eval) 836 } 837 if eval.JobID != jobID { 838 t.Fatalf("bad: %#v", eval) 839 } 840 if eval.JobModifyIndex != resp2.JobModifyIndex { 841 t.Fatalf("bad: %#v", eval) 842 } 843 if eval.Status != structs.EvalStatusPending { 844 t.Fatalf("bad: %#v", eval) 845 } 846 } 847 848 func TestJobEndpoint_Deregister_Periodic(t *testing.T) { 849 s1 := testServer(t, func(c *Config) { 850 c.NumSchedulers = 0 // Prevent automatic dequeue 851 }) 852 defer s1.Shutdown() 853 codec := rpcClient(t, s1) 854 testutil.WaitForLeader(t, s1.RPC) 855 856 // Create the register request 857 job := mock.PeriodicJob() 858 reg := &structs.JobRegisterRequest{ 859 Job: job, 860 WriteRequest: structs.WriteRequest{Region: "global"}, 861 } 862 863 // Fetch the response 864 var resp structs.JobRegisterResponse 865 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 866 t.Fatalf("err: %v", err) 867 } 868 869 // Deregister 870 dereg := &structs.JobDeregisterRequest{ 871 JobID: job.ID, 872 WriteRequest: structs.WriteRequest{Region: "global"}, 873 } 874 var resp2 structs.JobDeregisterResponse 875 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 876 t.Fatalf("err: %v", err) 877 } 878 if resp2.JobModifyIndex == 0 { 879 t.Fatalf("bad index: %d", resp2.Index) 880 } 881 882 // Check for the node in the FSM 883 state := s1.fsm.State() 884 out, err := state.JobByID(job.ID) 885 if err != nil { 886 t.Fatalf("err: %v", err) 887 } 888 if out != nil { 889 t.Fatalf("unexpected job") 890 } 891 892 if resp.EvalID != "" { 893 t.Fatalf("Deregister created an eval for a periodic job") 894 } 895 } 896 897 func TestJobEndpoint_GetJob(t *testing.T) { 898 s1 := testServer(t, nil) 899 defer s1.Shutdown() 900 codec := rpcClient(t, s1) 901 testutil.WaitForLeader(t, s1.RPC) 902 903 // Create the register request 904 job := mock.Job() 905 reg := &structs.JobRegisterRequest{ 906 Job: job, 907 WriteRequest: structs.WriteRequest{Region: "global"}, 908 } 909 910 // Fetch the response 911 var resp structs.JobRegisterResponse 912 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 913 t.Fatalf("err: %v", err) 914 } 915 job.CreateIndex = resp.JobModifyIndex 916 job.ModifyIndex = resp.JobModifyIndex 917 job.JobModifyIndex = resp.JobModifyIndex 918 919 // Lookup the job 920 get := &structs.JobSpecificRequest{ 921 JobID: job.ID, 922 QueryOptions: structs.QueryOptions{Region: "global"}, 923 } 924 var resp2 structs.SingleJobResponse 925 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 926 t.Fatalf("err: %v", err) 927 } 928 if resp2.Index != resp.JobModifyIndex { 929 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 930 } 931 932 // Make a copy of the origin job and change the service name so that we can 933 // do a deep equal with the response from the GET JOB Api 934 j := job 935 j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend" 936 for tgix, tg := range j.TaskGroups { 937 for tidx, t := range tg.Tasks { 938 for sidx, service := range t.Services { 939 for cidx, check := range service.Checks { 940 check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name 941 } 942 } 943 } 944 } 945 946 if !reflect.DeepEqual(j, resp2.Job) { 947 t.Fatalf("bad: %#v %#v", job, resp2.Job) 948 } 949 950 // Lookup non-existing job 951 get.JobID = "foobarbaz" 952 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 953 t.Fatalf("err: %v", err) 954 } 955 if resp2.Index != resp.JobModifyIndex { 956 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 957 } 958 if resp2.Job != nil { 959 t.Fatalf("unexpected job") 960 } 961 } 962 963 func TestJobEndpoint_GetJobSummary(t *testing.T) { 964 s1 := testServer(t, func(c *Config) { 965 c.NumSchedulers = 0 // Prevent automatic dequeue 966 }) 967 968 defer s1.Shutdown() 969 codec := rpcClient(t, s1) 970 testutil.WaitForLeader(t, s1.RPC) 971 972 // Create the register request 973 job := mock.Job() 974 reg := &structs.JobRegisterRequest{ 975 Job: job, 976 WriteRequest: structs.WriteRequest{Region: "global"}, 977 } 978 979 // Fetch the response 980 var resp structs.JobRegisterResponse 981 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 982 t.Fatalf("err: %v", err) 983 } 984 job.CreateIndex = resp.JobModifyIndex 985 job.ModifyIndex = resp.JobModifyIndex 986 job.JobModifyIndex = resp.JobModifyIndex 987 988 // Lookup the job summary 989 get := &structs.JobSummaryRequest{ 990 JobID: job.ID, 991 QueryOptions: structs.QueryOptions{Region: "global"}, 992 } 993 var resp2 structs.JobSummaryResponse 994 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil { 995 t.Fatalf("err: %v", err) 996 } 997 if resp2.Index != resp.JobModifyIndex { 998 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 999 } 1000 1001 expectedJobSummary := structs.JobSummary{ 1002 JobID: job.ID, 1003 Summary: map[string]structs.TaskGroupSummary{ 1004 "web": structs.TaskGroupSummary{}, 1005 }, 1006 CreateIndex: job.CreateIndex, 1007 ModifyIndex: job.CreateIndex, 1008 } 1009 1010 if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) { 1011 t.Fatalf("exptected: %v, actual: %v", expectedJobSummary, resp2.JobSummary) 1012 } 1013 } 1014 1015 func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) { 1016 s1 := testServer(t, nil) 1017 defer s1.Shutdown() 1018 state := s1.fsm.State() 1019 codec := rpcClient(t, s1) 1020 testutil.WaitForLeader(t, s1.RPC) 1021 1022 // Create a job and insert it 1023 job1 := mock.Job() 1024 time.AfterFunc(200*time.Millisecond, func() { 1025 if err := state.UpsertJob(100, job1); err != nil { 1026 t.Fatalf("err: %v", err) 1027 } 1028 }) 1029 1030 // Ensure the job summary request gets fired 1031 req := &structs.JobSummaryRequest{ 1032 JobID: job1.ID, 1033 QueryOptions: structs.QueryOptions{ 1034 Region: "global", 1035 MinQueryIndex: 50, 1036 }, 1037 } 1038 var resp structs.JobSummaryResponse 1039 start := time.Now() 1040 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil { 1041 t.Fatalf("err: %v", err) 1042 } 1043 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1044 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1045 } 1046 1047 // Upsert an allocation for the job which should trigger the watch. 1048 time.AfterFunc(200*time.Millisecond, func() { 1049 alloc := mock.Alloc() 1050 alloc.JobID = job1.ID 1051 alloc.Job = job1 1052 if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil { 1053 t.Fatalf("err: %v", err) 1054 } 1055 }) 1056 req = &structs.JobSummaryRequest{ 1057 JobID: job1.ID, 1058 QueryOptions: structs.QueryOptions{ 1059 Region: "global", 1060 MinQueryIndex: 199, 1061 }, 1062 } 1063 start = time.Now() 1064 var resp1 structs.JobSummaryResponse 1065 start = time.Now() 1066 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil { 1067 t.Fatalf("err: %v", err) 1068 } 1069 1070 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1071 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1072 } 1073 if resp1.Index != 200 { 1074 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1075 } 1076 if resp1.JobSummary == nil { 1077 t.Fatalf("bad: %#v", resp) 1078 } 1079 1080 // Job delete fires watches 1081 time.AfterFunc(100*time.Millisecond, func() { 1082 if err := state.DeleteJob(300, job1.ID); err != nil { 1083 t.Fatalf("err: %v", err) 1084 } 1085 }) 1086 1087 req.QueryOptions.MinQueryIndex = 250 1088 start = time.Now() 1089 1090 var resp2 structs.SingleJobResponse 1091 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil { 1092 t.Fatalf("err: %v", err) 1093 } 1094 1095 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1096 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1097 } 1098 if resp2.Index != 300 { 1099 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 1100 } 1101 if resp2.Job != nil { 1102 t.Fatalf("bad: %#v", resp2.Job) 1103 } 1104 } 1105 1106 func TestJobEndpoint_GetJob_Blocking(t *testing.T) { 1107 s1 := testServer(t, nil) 1108 defer s1.Shutdown() 1109 state := s1.fsm.State() 1110 codec := rpcClient(t, s1) 1111 testutil.WaitForLeader(t, s1.RPC) 1112 1113 // Create the jobs 1114 job1 := mock.Job() 1115 job2 := mock.Job() 1116 1117 // Upsert a job we are not interested in first. 1118 time.AfterFunc(100*time.Millisecond, func() { 1119 if err := state.UpsertJob(100, job1); err != nil { 1120 t.Fatalf("err: %v", err) 1121 } 1122 }) 1123 1124 // Upsert another job later which should trigger the watch. 1125 time.AfterFunc(200*time.Millisecond, func() { 1126 if err := state.UpsertJob(200, job2); err != nil { 1127 t.Fatalf("err: %v", err) 1128 } 1129 }) 1130 1131 req := &structs.JobSpecificRequest{ 1132 JobID: job2.ID, 1133 QueryOptions: structs.QueryOptions{ 1134 Region: "global", 1135 MinQueryIndex: 50, 1136 }, 1137 } 1138 start := time.Now() 1139 var resp structs.SingleJobResponse 1140 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil { 1141 t.Fatalf("err: %v", err) 1142 } 1143 1144 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1145 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1146 } 1147 if resp.Index != 200 { 1148 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1149 } 1150 if resp.Job == nil || resp.Job.ID != job2.ID { 1151 t.Fatalf("bad: %#v", resp.Job) 1152 } 1153 1154 // Job delete fires watches 1155 time.AfterFunc(100*time.Millisecond, func() { 1156 if err := state.DeleteJob(300, job2.ID); err != nil { 1157 t.Fatalf("err: %v", err) 1158 } 1159 }) 1160 1161 req.QueryOptions.MinQueryIndex = 250 1162 start = time.Now() 1163 1164 var resp2 structs.SingleJobResponse 1165 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil { 1166 t.Fatalf("err: %v", err) 1167 } 1168 1169 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1170 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1171 } 1172 if resp2.Index != 300 { 1173 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 1174 } 1175 if resp2.Job != nil { 1176 t.Fatalf("bad: %#v", resp2.Job) 1177 } 1178 } 1179 1180 func TestJobEndpoint_ListJobs(t *testing.T) { 1181 s1 := testServer(t, nil) 1182 defer s1.Shutdown() 1183 codec := rpcClient(t, s1) 1184 testutil.WaitForLeader(t, s1.RPC) 1185 1186 // Create the register request 1187 job := mock.Job() 1188 state := s1.fsm.State() 1189 err := state.UpsertJob(1000, job) 1190 if err != nil { 1191 t.Fatalf("err: %v", err) 1192 } 1193 1194 // Lookup the jobs 1195 get := &structs.JobListRequest{ 1196 QueryOptions: structs.QueryOptions{Region: "global"}, 1197 } 1198 var resp2 structs.JobListResponse 1199 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil { 1200 t.Fatalf("err: %v", err) 1201 } 1202 if resp2.Index != 1000 { 1203 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1204 } 1205 1206 if len(resp2.Jobs) != 1 { 1207 t.Fatalf("bad: %#v", resp2.Jobs) 1208 } 1209 if resp2.Jobs[0].ID != job.ID { 1210 t.Fatalf("bad: %#v", resp2.Jobs[0]) 1211 } 1212 1213 // Lookup the jobs by prefix 1214 get = &structs.JobListRequest{ 1215 QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]}, 1216 } 1217 var resp3 structs.JobListResponse 1218 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil { 1219 t.Fatalf("err: %v", err) 1220 } 1221 if resp3.Index != 1000 { 1222 t.Fatalf("Bad index: %d %d", resp3.Index, 1000) 1223 } 1224 1225 if len(resp3.Jobs) != 1 { 1226 t.Fatalf("bad: %#v", resp3.Jobs) 1227 } 1228 if resp3.Jobs[0].ID != job.ID { 1229 t.Fatalf("bad: %#v", resp3.Jobs[0]) 1230 } 1231 } 1232 1233 func TestJobEndpoint_ListJobs_Blocking(t *testing.T) { 1234 s1 := testServer(t, nil) 1235 defer s1.Shutdown() 1236 state := s1.fsm.State() 1237 codec := rpcClient(t, s1) 1238 testutil.WaitForLeader(t, s1.RPC) 1239 1240 // Create the job 1241 job := mock.Job() 1242 1243 // Upsert job triggers watches 1244 time.AfterFunc(100*time.Millisecond, func() { 1245 if err := state.UpsertJob(100, job); err != nil { 1246 t.Fatalf("err: %v", err) 1247 } 1248 }) 1249 1250 req := &structs.JobListRequest{ 1251 QueryOptions: structs.QueryOptions{ 1252 Region: "global", 1253 MinQueryIndex: 50, 1254 }, 1255 } 1256 start := time.Now() 1257 var resp structs.JobListResponse 1258 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil { 1259 t.Fatalf("err: %v", err) 1260 } 1261 1262 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1263 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1264 } 1265 if resp.Index != 100 { 1266 t.Fatalf("Bad index: %d %d", resp.Index, 100) 1267 } 1268 if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID { 1269 t.Fatalf("bad: %#v", resp.Jobs) 1270 } 1271 1272 // Job deletion triggers watches 1273 time.AfterFunc(100*time.Millisecond, func() { 1274 if err := state.DeleteJob(200, job.ID); err != nil { 1275 t.Fatalf("err: %v", err) 1276 } 1277 }) 1278 1279 req.MinQueryIndex = 150 1280 start = time.Now() 1281 var resp2 structs.JobListResponse 1282 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil { 1283 t.Fatalf("err: %v", err) 1284 } 1285 1286 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1287 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1288 } 1289 if resp2.Index != 200 { 1290 t.Fatalf("Bad index: %d %d", resp2.Index, 200) 1291 } 1292 if len(resp2.Jobs) != 0 { 1293 t.Fatalf("bad: %#v", resp2.Jobs) 1294 } 1295 } 1296 1297 func TestJobEndpoint_Allocations(t *testing.T) { 1298 s1 := testServer(t, nil) 1299 defer s1.Shutdown() 1300 codec := rpcClient(t, s1) 1301 testutil.WaitForLeader(t, s1.RPC) 1302 1303 // Create the register request 1304 alloc1 := mock.Alloc() 1305 alloc2 := mock.Alloc() 1306 alloc2.JobID = alloc1.JobID 1307 state := s1.fsm.State() 1308 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 1309 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 1310 err := state.UpsertAllocs(1000, 1311 []*structs.Allocation{alloc1, alloc2}) 1312 if err != nil { 1313 t.Fatalf("err: %v", err) 1314 } 1315 1316 // Lookup the jobs 1317 get := &structs.JobSpecificRequest{ 1318 JobID: alloc1.JobID, 1319 QueryOptions: structs.QueryOptions{Region: "global"}, 1320 } 1321 var resp2 structs.JobAllocationsResponse 1322 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil { 1323 t.Fatalf("err: %v", err) 1324 } 1325 if resp2.Index != 1000 { 1326 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1327 } 1328 1329 if len(resp2.Allocations) != 2 { 1330 t.Fatalf("bad: %#v", resp2.Allocations) 1331 } 1332 } 1333 1334 func TestJobEndpoint_Allocations_Blocking(t *testing.T) { 1335 s1 := testServer(t, nil) 1336 defer s1.Shutdown() 1337 codec := rpcClient(t, s1) 1338 testutil.WaitForLeader(t, s1.RPC) 1339 1340 // Create the register request 1341 alloc1 := mock.Alloc() 1342 alloc2 := mock.Alloc() 1343 alloc2.JobID = "job1" 1344 state := s1.fsm.State() 1345 1346 // First upsert an unrelated alloc 1347 time.AfterFunc(100*time.Millisecond, func() { 1348 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 1349 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 1350 if err != nil { 1351 t.Fatalf("err: %v", err) 1352 } 1353 }) 1354 1355 // Upsert an alloc for the job we are interested in later 1356 time.AfterFunc(200*time.Millisecond, func() { 1357 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 1358 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 1359 if err != nil { 1360 t.Fatalf("err: %v", err) 1361 } 1362 }) 1363 1364 // Lookup the jobs 1365 get := &structs.JobSpecificRequest{ 1366 JobID: "job1", 1367 QueryOptions: structs.QueryOptions{ 1368 Region: "global", 1369 MinQueryIndex: 50, 1370 }, 1371 } 1372 var resp structs.JobAllocationsResponse 1373 start := time.Now() 1374 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil { 1375 t.Fatalf("err: %v", err) 1376 } 1377 1378 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1379 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1380 } 1381 if resp.Index != 200 { 1382 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1383 } 1384 if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" { 1385 t.Fatalf("bad: %#v", resp.Allocations) 1386 } 1387 } 1388 1389 func TestJobEndpoint_Evaluations(t *testing.T) { 1390 s1 := testServer(t, nil) 1391 defer s1.Shutdown() 1392 codec := rpcClient(t, s1) 1393 testutil.WaitForLeader(t, s1.RPC) 1394 1395 // Create the register request 1396 eval1 := mock.Eval() 1397 eval2 := mock.Eval() 1398 eval2.JobID = eval1.JobID 1399 state := s1.fsm.State() 1400 err := state.UpsertEvals(1000, 1401 []*structs.Evaluation{eval1, eval2}) 1402 if err != nil { 1403 t.Fatalf("err: %v", err) 1404 } 1405 1406 // Lookup the jobs 1407 get := &structs.JobSpecificRequest{ 1408 JobID: eval1.JobID, 1409 QueryOptions: structs.QueryOptions{Region: "global"}, 1410 } 1411 var resp2 structs.JobEvaluationsResponse 1412 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil { 1413 t.Fatalf("err: %v", err) 1414 } 1415 if resp2.Index != 1000 { 1416 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1417 } 1418 1419 if len(resp2.Evaluations) != 2 { 1420 t.Fatalf("bad: %#v", resp2.Evaluations) 1421 } 1422 } 1423 1424 func TestJobEndpoint_Evaluations_Blocking(t *testing.T) { 1425 s1 := testServer(t, nil) 1426 defer s1.Shutdown() 1427 codec := rpcClient(t, s1) 1428 testutil.WaitForLeader(t, s1.RPC) 1429 1430 // Create the register request 1431 eval1 := mock.Eval() 1432 eval2 := mock.Eval() 1433 eval2.JobID = "job1" 1434 state := s1.fsm.State() 1435 1436 // First upsert an unrelated eval 1437 time.AfterFunc(100*time.Millisecond, func() { 1438 err := state.UpsertEvals(100, []*structs.Evaluation{eval1}) 1439 if err != nil { 1440 t.Fatalf("err: %v", err) 1441 } 1442 }) 1443 1444 // Upsert an eval for the job we are interested in later 1445 time.AfterFunc(200*time.Millisecond, func() { 1446 err := state.UpsertEvals(200, []*structs.Evaluation{eval2}) 1447 if err != nil { 1448 t.Fatalf("err: %v", err) 1449 } 1450 }) 1451 1452 // Lookup the jobs 1453 get := &structs.JobSpecificRequest{ 1454 JobID: "job1", 1455 QueryOptions: structs.QueryOptions{ 1456 Region: "global", 1457 MinQueryIndex: 50, 1458 }, 1459 } 1460 var resp structs.JobEvaluationsResponse 1461 start := time.Now() 1462 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil { 1463 t.Fatalf("err: %v", err) 1464 } 1465 1466 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1467 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1468 } 1469 if resp.Index != 200 { 1470 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1471 } 1472 if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" { 1473 t.Fatalf("bad: %#v", resp.Evaluations) 1474 } 1475 } 1476 1477 func TestJobEndpoint_Plan_WithDiff(t *testing.T) { 1478 s1 := testServer(t, func(c *Config) { 1479 c.NumSchedulers = 0 // Prevent automatic dequeue 1480 }) 1481 defer s1.Shutdown() 1482 codec := rpcClient(t, s1) 1483 testutil.WaitForLeader(t, s1.RPC) 1484 1485 // Create the register request 1486 job := mock.Job() 1487 req := &structs.JobRegisterRequest{ 1488 Job: job, 1489 WriteRequest: structs.WriteRequest{Region: "global"}, 1490 } 1491 1492 // Fetch the response 1493 var resp structs.JobRegisterResponse 1494 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1495 t.Fatalf("err: %v", err) 1496 } 1497 if resp.Index == 0 { 1498 t.Fatalf("bad index: %d", resp.Index) 1499 } 1500 1501 // Create a plan request 1502 planReq := &structs.JobPlanRequest{ 1503 Job: job, 1504 Diff: true, 1505 WriteRequest: structs.WriteRequest{Region: "global"}, 1506 } 1507 1508 // Fetch the response 1509 var planResp structs.JobPlanResponse 1510 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 1511 t.Fatalf("err: %v", err) 1512 } 1513 1514 // Check the response 1515 if planResp.JobModifyIndex == 0 { 1516 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 1517 } 1518 if planResp.Annotations == nil { 1519 t.Fatalf("no annotations") 1520 } 1521 if planResp.Diff == nil { 1522 t.Fatalf("no diff") 1523 } 1524 if len(planResp.FailedTGAllocs) == 0 { 1525 t.Fatalf("no failed task group alloc metrics") 1526 } 1527 } 1528 1529 func TestJobEndpoint_Plan_NoDiff(t *testing.T) { 1530 s1 := testServer(t, func(c *Config) { 1531 c.NumSchedulers = 0 // Prevent automatic dequeue 1532 }) 1533 defer s1.Shutdown() 1534 codec := rpcClient(t, s1) 1535 testutil.WaitForLeader(t, s1.RPC) 1536 1537 // Create the register request 1538 job := mock.Job() 1539 req := &structs.JobRegisterRequest{ 1540 Job: job, 1541 WriteRequest: structs.WriteRequest{Region: "global"}, 1542 } 1543 1544 // Fetch the response 1545 var resp structs.JobRegisterResponse 1546 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1547 t.Fatalf("err: %v", err) 1548 } 1549 if resp.Index == 0 { 1550 t.Fatalf("bad index: %d", resp.Index) 1551 } 1552 1553 // Create a plan request 1554 planReq := &structs.JobPlanRequest{ 1555 Job: job, 1556 Diff: false, 1557 WriteRequest: structs.WriteRequest{Region: "global"}, 1558 } 1559 1560 // Fetch the response 1561 var planResp structs.JobPlanResponse 1562 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 1563 t.Fatalf("err: %v", err) 1564 } 1565 1566 // Check the response 1567 if planResp.JobModifyIndex == 0 { 1568 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 1569 } 1570 if planResp.Annotations == nil { 1571 t.Fatalf("no annotations") 1572 } 1573 if planResp.Diff != nil { 1574 t.Fatalf("got diff") 1575 } 1576 if len(planResp.FailedTGAllocs) == 0 { 1577 t.Fatalf("no failed task group alloc metrics") 1578 } 1579 } 1580 1581 func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) { 1582 s1 := testServer(t, func(c *Config) { 1583 c.NumSchedulers = 0 // Prevent automatic dequeue 1584 }) 1585 defer s1.Shutdown() 1586 codec := rpcClient(t, s1) 1587 testutil.WaitForLeader(t, s1.RPC) 1588 1589 // Enable vault 1590 tr, f := true, false 1591 s1.config.VaultConfig.Enabled = &tr 1592 s1.config.VaultConfig.AllowUnauthenticated = &f 1593 1594 // Replace the Vault Client on the server 1595 tvc := &TestVaultClient{} 1596 s1.vault = tvc 1597 1598 policy := "foo" 1599 goodToken := structs.GenerateUUID() 1600 goodPolicies := []string{"foo", "bar", "baz"} 1601 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 1602 1603 // Create the register request with a job asking for a vault policy 1604 job := mock.Job() 1605 job.VaultToken = goodToken 1606 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 1607 Policies: []string{policy}, 1608 ChangeMode: structs.VaultChangeModeRestart, 1609 } 1610 req := &structs.JobRegisterRequest{ 1611 Job: job, 1612 WriteRequest: structs.WriteRequest{Region: "global"}, 1613 } 1614 1615 // Fetch the response 1616 var resp structs.JobRegisterResponse 1617 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1618 t.Fatalf("bad: %v", err) 1619 } 1620 1621 // Check for the job in the FSM 1622 state := s1.fsm.State() 1623 out, err := state.JobByID(job.ID) 1624 if err != nil { 1625 t.Fatalf("err: %v", err) 1626 } 1627 if out == nil { 1628 t.Fatalf("expected job") 1629 } 1630 if out.CreateIndex != resp.JobModifyIndex { 1631 t.Fatalf("index mis-match") 1632 } 1633 1634 // Check that there is an implicit vault constraint 1635 constraints := out.TaskGroups[0].Constraints 1636 if len(constraints) != 1 { 1637 t.Fatalf("Expected an implicit constraint") 1638 } 1639 1640 if !constraints[0].Equal(vaultConstraint) { 1641 t.Fatalf("Expected implicit vault constraint") 1642 } 1643 } 1644 1645 func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) { 1646 s1 := testServer(t, func(c *Config) { 1647 c.NumSchedulers = 0 // Prevent automatic dequeue 1648 }) 1649 defer s1.Shutdown() 1650 codec := rpcClient(t, s1) 1651 testutil.WaitForLeader(t, s1.RPC) 1652 1653 // Create the register request with a job asking for a template that sends a 1654 // signal 1655 job := mock.Job() 1656 signal := "SIGUSR1" 1657 job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{ 1658 &structs.Template{ 1659 SourcePath: "foo", 1660 DestPath: "bar", 1661 ChangeMode: structs.TemplateChangeModeSignal, 1662 ChangeSignal: signal, 1663 }, 1664 } 1665 req := &structs.JobRegisterRequest{ 1666 Job: job, 1667 WriteRequest: structs.WriteRequest{Region: "global"}, 1668 } 1669 1670 // Fetch the response 1671 var resp structs.JobRegisterResponse 1672 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1673 t.Fatalf("bad: %v", err) 1674 } 1675 1676 // Check for the job in the FSM 1677 state := s1.fsm.State() 1678 out, err := state.JobByID(job.ID) 1679 if err != nil { 1680 t.Fatalf("err: %v", err) 1681 } 1682 if out == nil { 1683 t.Fatalf("expected job") 1684 } 1685 if out.CreateIndex != resp.JobModifyIndex { 1686 t.Fatalf("index mis-match") 1687 } 1688 1689 // Check that there is an implicit signal constraint 1690 constraints := out.TaskGroups[0].Constraints 1691 if len(constraints) != 1 { 1692 t.Fatalf("Expected an implicit constraint") 1693 } 1694 1695 sigConstraint := getSignalConstraint([]string{signal}) 1696 1697 if !constraints[0].Equal(sigConstraint) { 1698 t.Fatalf("Expected implicit vault constraint") 1699 } 1700 } 1701 1702 func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) { 1703 // Create a mock job with an invalid config 1704 job := mock.Job() 1705 job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{ 1706 "foo": "bar", 1707 } 1708 1709 if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "-> config") { 1710 t.Fatalf("Expected config error; got %v", err) 1711 } 1712 } 1713 1714 func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) { 1715 // Create a mock job that wants to send a signal to a driver that can't 1716 job := mock.Job() 1717 job.TaskGroups[0].Tasks[0].Driver = "qemu" 1718 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 1719 Policies: []string{"foo"}, 1720 ChangeMode: structs.VaultChangeModeSignal, 1721 ChangeSignal: "SIGUSR1", 1722 } 1723 1724 if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "support sending signals") { 1725 t.Fatalf("Expected signal feasibility error; got %v", err) 1726 } 1727 }