github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/job_endpoint_test.go (about) 1 package nomad 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 "time" 9 10 memdb "github.com/hashicorp/go-memdb" 11 msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" 12 "github.com/hashicorp/nomad/acl" 13 "github.com/hashicorp/nomad/helper" 14 "github.com/hashicorp/nomad/helper/uuid" 15 "github.com/hashicorp/nomad/nomad/mock" 16 "github.com/hashicorp/nomad/nomad/structs" 17 "github.com/hashicorp/nomad/testutil" 18 "github.com/kr/pretty" 19 "github.com/stretchr/testify/require" 20 ) 21 22 func TestJobEndpoint_Register(t *testing.T) { 23 t.Parallel() 24 s1 := TestServer(t, func(c *Config) { 25 c.NumSchedulers = 0 // Prevent automatic dequeue 26 }) 27 defer s1.Shutdown() 28 codec := rpcClient(t, s1) 29 testutil.WaitForLeader(t, s1.RPC) 30 31 // Create the register request 32 job := mock.Job() 33 req := &structs.JobRegisterRequest{ 34 Job: job, 35 WriteRequest: structs.WriteRequest{ 36 Region: "global", 37 Namespace: job.Namespace, 38 }, 39 } 40 41 // Fetch the response 42 var resp structs.JobRegisterResponse 43 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 44 t.Fatalf("err: %v", err) 45 } 46 if resp.Index == 0 { 47 t.Fatalf("bad index: %d", resp.Index) 48 } 49 50 // Check for the node in the FSM 51 state := s1.fsm.State() 52 ws := memdb.NewWatchSet() 53 out, err := state.JobByID(ws, job.Namespace, job.ID) 54 if err != nil { 55 t.Fatalf("err: %v", err) 56 } 57 if out == nil { 58 t.Fatalf("expected job") 59 } 60 if out.CreateIndex != resp.JobModifyIndex { 61 t.Fatalf("index mis-match") 62 } 63 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 64 expectedServiceName := "web-frontend" 65 if serviceName != expectedServiceName { 66 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 67 } 68 69 // Lookup the evaluation 70 eval, err := state.EvalByID(ws, resp.EvalID) 71 if err != nil { 72 t.Fatalf("err: %v", err) 73 } 74 if eval == nil { 75 t.Fatalf("expected eval") 76 } 77 if eval.CreateIndex != resp.EvalCreateIndex { 78 t.Fatalf("index mis-match") 79 } 80 81 if eval.Priority != job.Priority { 82 t.Fatalf("bad: %#v", eval) 83 } 84 if eval.Type != job.Type { 85 t.Fatalf("bad: %#v", eval) 86 } 87 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 88 t.Fatalf("bad: %#v", eval) 89 } 90 if eval.JobID != job.ID { 91 t.Fatalf("bad: %#v", eval) 92 } 93 if eval.JobModifyIndex != resp.JobModifyIndex { 94 t.Fatalf("bad: %#v", eval) 95 } 96 if eval.Status != structs.EvalStatusPending { 97 t.Fatalf("bad: %#v", eval) 98 } 99 } 100 101 func TestJobEndpoint_Register_ACL(t *testing.T) { 102 t.Parallel() 103 s1, root := TestACLServer(t, func(c *Config) { 104 c.NumSchedulers = 0 // Prevent automatic dequeue 105 }) 106 defer s1.Shutdown() 107 codec := rpcClient(t, s1) 108 testutil.WaitForLeader(t, s1.RPC) 109 110 // Create the register request 111 job := mock.Job() 112 req := &structs.JobRegisterRequest{ 113 Job: job, 114 WriteRequest: structs.WriteRequest{Region: "global"}, 115 } 116 117 // Try without a token, expect failure 118 var resp structs.JobRegisterResponse 119 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err == nil { 120 t.Fatalf("expected error") 121 } 122 123 // Try with a token 124 req.AuthToken = root.SecretID 125 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 126 t.Fatalf("err: %v", err) 127 } 128 if resp.Index == 0 { 129 t.Fatalf("bad index: %d", resp.Index) 130 } 131 132 // Check for the node in the FSM 133 state := s1.fsm.State() 134 ws := memdb.NewWatchSet() 135 out, err := state.JobByID(ws, job.Namespace, job.ID) 136 if err != nil { 137 t.Fatalf("err: %v", err) 138 } 139 if out == nil { 140 t.Fatalf("expected job") 141 } 142 } 143 144 func TestJobEndpoint_Register_InvalidNamespace(t *testing.T) { 145 t.Parallel() 146 s1 := TestServer(t, func(c *Config) { 147 c.NumSchedulers = 0 // Prevent automatic dequeue 148 }) 149 defer s1.Shutdown() 150 codec := rpcClient(t, s1) 151 testutil.WaitForLeader(t, s1.RPC) 152 153 // Create the register request 154 job := mock.Job() 155 job.Namespace = "foo" 156 req := &structs.JobRegisterRequest{ 157 Job: job, 158 WriteRequest: structs.WriteRequest{Region: "global"}, 159 } 160 161 // Try without a token, expect failure 162 var resp structs.JobRegisterResponse 163 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 164 if err == nil || !strings.Contains(err.Error(), "nonexistent namespace") { 165 t.Fatalf("expected namespace error: %v", err) 166 } 167 168 // Check for the job in the FSM 169 state := s1.fsm.State() 170 ws := memdb.NewWatchSet() 171 out, err := state.JobByID(ws, job.Namespace, job.ID) 172 if err != nil { 173 t.Fatalf("err: %v", err) 174 } 175 if out != nil { 176 t.Fatalf("expected no job") 177 } 178 } 179 180 func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) { 181 t.Parallel() 182 s1 := TestServer(t, func(c *Config) { 183 c.NumSchedulers = 0 // Prevent automatic dequeue 184 }) 185 defer s1.Shutdown() 186 codec := rpcClient(t, s1) 187 testutil.WaitForLeader(t, s1.RPC) 188 189 // Create the register request with a job containing an invalid driver 190 // config 191 job := mock.Job() 192 job.TaskGroups[0].Tasks[0].Config["foo"] = 1 193 req := &structs.JobRegisterRequest{ 194 Job: job, 195 WriteRequest: structs.WriteRequest{ 196 Region: "global", 197 Namespace: job.Namespace, 198 }, 199 } 200 201 // Fetch the response 202 var resp structs.JobRegisterResponse 203 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 204 if err == nil { 205 t.Fatalf("expected a validation error") 206 } 207 208 if !strings.Contains(err.Error(), "-> config:") { 209 t.Fatalf("expected a driver config validation error but got: %v", err) 210 } 211 } 212 213 func TestJobEndpoint_Register_Payload(t *testing.T) { 214 t.Parallel() 215 s1 := TestServer(t, func(c *Config) { 216 c.NumSchedulers = 0 // Prevent automatic dequeue 217 }) 218 defer s1.Shutdown() 219 codec := rpcClient(t, s1) 220 testutil.WaitForLeader(t, s1.RPC) 221 222 // Create the register request with a job containing an invalid driver 223 // config 224 job := mock.Job() 225 job.Payload = []byte{0x1} 226 req := &structs.JobRegisterRequest{ 227 Job: job, 228 WriteRequest: structs.WriteRequest{ 229 Region: "global", 230 Namespace: job.Namespace, 231 }, 232 } 233 234 // Fetch the response 235 var resp structs.JobRegisterResponse 236 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 237 if err == nil { 238 t.Fatalf("expected a validation error") 239 } 240 241 if !strings.Contains(err.Error(), "payload") { 242 t.Fatalf("expected a payload error but got: %v", err) 243 } 244 } 245 246 func TestJobEndpoint_Register_Existing(t *testing.T) { 247 t.Parallel() 248 s1 := TestServer(t, func(c *Config) { 249 c.NumSchedulers = 0 // Prevent automatic dequeue 250 }) 251 defer s1.Shutdown() 252 codec := rpcClient(t, s1) 253 testutil.WaitForLeader(t, s1.RPC) 254 255 // Create the register request 256 job := mock.Job() 257 req := &structs.JobRegisterRequest{ 258 Job: job, 259 WriteRequest: structs.WriteRequest{ 260 Region: "global", 261 Namespace: job.Namespace, 262 }, 263 } 264 265 // Fetch the response 266 var resp structs.JobRegisterResponse 267 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 268 t.Fatalf("err: %v", err) 269 } 270 if resp.Index == 0 { 271 t.Fatalf("bad index: %d", resp.Index) 272 } 273 274 // Update the job definition 275 job2 := mock.Job() 276 job2.Priority = 100 277 job2.ID = job.ID 278 req.Job = job2 279 280 // Attempt update 281 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 282 t.Fatalf("err: %v", err) 283 } 284 if resp.Index == 0 { 285 t.Fatalf("bad index: %d", resp.Index) 286 } 287 288 // Check for the node in the FSM 289 state := s1.fsm.State() 290 ws := memdb.NewWatchSet() 291 out, err := state.JobByID(ws, job.Namespace, job.ID) 292 if err != nil { 293 t.Fatalf("err: %v", err) 294 } 295 if out == nil { 296 t.Fatalf("expected job") 297 } 298 if out.ModifyIndex != resp.JobModifyIndex { 299 t.Fatalf("index mis-match") 300 } 301 if out.Priority != 100 { 302 t.Fatalf("expected update") 303 } 304 if out.Version != 1 { 305 t.Fatalf("expected update") 306 } 307 308 // Lookup the evaluation 309 eval, err := state.EvalByID(ws, resp.EvalID) 310 if err != nil { 311 t.Fatalf("err: %v", err) 312 } 313 if eval == nil { 314 t.Fatalf("expected eval") 315 } 316 if eval.CreateIndex != resp.EvalCreateIndex { 317 t.Fatalf("index mis-match") 318 } 319 320 if eval.Priority != job2.Priority { 321 t.Fatalf("bad: %#v", eval) 322 } 323 if eval.Type != job2.Type { 324 t.Fatalf("bad: %#v", eval) 325 } 326 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 327 t.Fatalf("bad: %#v", eval) 328 } 329 if eval.JobID != job2.ID { 330 t.Fatalf("bad: %#v", eval) 331 } 332 if eval.JobModifyIndex != resp.JobModifyIndex { 333 t.Fatalf("bad: %#v", eval) 334 } 335 if eval.Status != structs.EvalStatusPending { 336 t.Fatalf("bad: %#v", eval) 337 } 338 339 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 340 t.Fatalf("err: %v", err) 341 } 342 if resp.Index == 0 { 343 t.Fatalf("bad index: %d", resp.Index) 344 } 345 346 // Check to ensure the job version didn't get bumped because we submitted 347 // the same job 348 state = s1.fsm.State() 349 ws = memdb.NewWatchSet() 350 out, err = state.JobByID(ws, job.Namespace, job.ID) 351 if err != nil { 352 t.Fatalf("err: %v", err) 353 } 354 if out == nil { 355 t.Fatalf("expected job") 356 } 357 if out.Version != 1 { 358 t.Fatalf("expected no update; got %v; diff %v", out.Version, pretty.Diff(job2, out)) 359 } 360 } 361 362 func TestJobEndpoint_Register_Periodic(t *testing.T) { 363 t.Parallel() 364 s1 := TestServer(t, func(c *Config) { 365 c.NumSchedulers = 0 // Prevent automatic dequeue 366 }) 367 defer s1.Shutdown() 368 codec := rpcClient(t, s1) 369 testutil.WaitForLeader(t, s1.RPC) 370 371 // Create the register request for a periodic job. 372 job := mock.PeriodicJob() 373 req := &structs.JobRegisterRequest{ 374 Job: job, 375 WriteRequest: structs.WriteRequest{ 376 Region: "global", 377 Namespace: job.Namespace, 378 }, 379 } 380 381 // Fetch the response 382 var resp structs.JobRegisterResponse 383 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 384 t.Fatalf("err: %v", err) 385 } 386 if resp.JobModifyIndex == 0 { 387 t.Fatalf("bad index: %d", resp.Index) 388 } 389 390 // Check for the node in the FSM 391 state := s1.fsm.State() 392 ws := memdb.NewWatchSet() 393 out, err := state.JobByID(ws, job.Namespace, job.ID) 394 if err != nil { 395 t.Fatalf("err: %v", err) 396 } 397 if out == nil { 398 t.Fatalf("expected job") 399 } 400 if out.CreateIndex != resp.JobModifyIndex { 401 t.Fatalf("index mis-match") 402 } 403 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 404 expectedServiceName := "web-frontend" 405 if serviceName != expectedServiceName { 406 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 407 } 408 409 if resp.EvalID != "" { 410 t.Fatalf("Register created an eval for a periodic job") 411 } 412 } 413 414 func TestJobEndpoint_Register_ParameterizedJob(t *testing.T) { 415 t.Parallel() 416 s1 := TestServer(t, func(c *Config) { 417 c.NumSchedulers = 0 // Prevent automatic dequeue 418 }) 419 defer s1.Shutdown() 420 codec := rpcClient(t, s1) 421 testutil.WaitForLeader(t, s1.RPC) 422 423 // Create the register request for a parameterized job. 424 job := mock.BatchJob() 425 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 426 req := &structs.JobRegisterRequest{ 427 Job: job, 428 WriteRequest: structs.WriteRequest{ 429 Region: "global", 430 Namespace: job.Namespace, 431 }, 432 } 433 434 // Fetch the response 435 var resp structs.JobRegisterResponse 436 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 437 t.Fatalf("err: %v", err) 438 } 439 if resp.JobModifyIndex == 0 { 440 t.Fatalf("bad index: %d", resp.Index) 441 } 442 443 // Check for the job in the FSM 444 state := s1.fsm.State() 445 ws := memdb.NewWatchSet() 446 out, err := state.JobByID(ws, job.Namespace, job.ID) 447 if err != nil { 448 t.Fatalf("err: %v", err) 449 } 450 if out == nil { 451 t.Fatalf("expected job") 452 } 453 if out.CreateIndex != resp.JobModifyIndex { 454 t.Fatalf("index mis-match") 455 } 456 if resp.EvalID != "" { 457 t.Fatalf("Register created an eval for a parameterized job") 458 } 459 } 460 461 func TestJobEndpoint_Register_Dispatched(t *testing.T) { 462 t.Parallel() 463 require := require.New(t) 464 s1 := TestServer(t, func(c *Config) { 465 c.NumSchedulers = 0 // Prevent automatic dequeue 466 }) 467 defer s1.Shutdown() 468 codec := rpcClient(t, s1) 469 testutil.WaitForLeader(t, s1.RPC) 470 471 // Create the register request with a job with 'Dispatch' set to true 472 job := mock.Job() 473 job.Dispatched = true 474 req := &structs.JobRegisterRequest{ 475 Job: job, 476 WriteRequest: structs.WriteRequest{ 477 Region: "global", 478 Namespace: job.Namespace, 479 }, 480 } 481 482 // Fetch the response 483 var resp structs.JobRegisterResponse 484 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 485 require.Error(err) 486 require.Contains(err.Error(), "job can't be submitted with 'Dispatched'") 487 } 488 func TestJobEndpoint_Register_EnforceIndex(t *testing.T) { 489 t.Parallel() 490 s1 := TestServer(t, func(c *Config) { 491 c.NumSchedulers = 0 // Prevent automatic dequeue 492 }) 493 defer s1.Shutdown() 494 codec := rpcClient(t, s1) 495 testutil.WaitForLeader(t, s1.RPC) 496 497 // Create the register request and enforcing an incorrect index 498 job := mock.Job() 499 req := &structs.JobRegisterRequest{ 500 Job: job, 501 EnforceIndex: true, 502 JobModifyIndex: 100, // Not registered yet so not possible 503 WriteRequest: structs.WriteRequest{ 504 Region: "global", 505 Namespace: job.Namespace, 506 }, 507 } 508 509 // Fetch the response 510 var resp structs.JobRegisterResponse 511 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 512 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 513 t.Fatalf("expected enforcement error") 514 } 515 516 // Create the register request and enforcing it is new 517 req = &structs.JobRegisterRequest{ 518 Job: job, 519 EnforceIndex: true, 520 JobModifyIndex: 0, 521 WriteRequest: structs.WriteRequest{ 522 Region: "global", 523 Namespace: job.Namespace, 524 }, 525 } 526 527 // Fetch the response 528 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 529 t.Fatalf("err: %v", err) 530 } 531 if resp.Index == 0 { 532 t.Fatalf("bad index: %d", resp.Index) 533 } 534 535 curIndex := resp.JobModifyIndex 536 537 // Check for the node in the FSM 538 state := s1.fsm.State() 539 ws := memdb.NewWatchSet() 540 out, err := state.JobByID(ws, job.Namespace, job.ID) 541 if err != nil { 542 t.Fatalf("err: %v", err) 543 } 544 if out == nil { 545 t.Fatalf("expected job") 546 } 547 if out.CreateIndex != resp.JobModifyIndex { 548 t.Fatalf("index mis-match") 549 } 550 551 // Reregister request and enforcing it be a new job 552 req = &structs.JobRegisterRequest{ 553 Job: job, 554 EnforceIndex: true, 555 JobModifyIndex: 0, 556 WriteRequest: structs.WriteRequest{ 557 Region: "global", 558 Namespace: job.Namespace, 559 }, 560 } 561 562 // Fetch the response 563 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 564 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 565 t.Fatalf("expected enforcement error") 566 } 567 568 // Reregister request and enforcing it be at an incorrect index 569 req = &structs.JobRegisterRequest{ 570 Job: job, 571 EnforceIndex: true, 572 JobModifyIndex: curIndex - 1, 573 WriteRequest: structs.WriteRequest{ 574 Region: "global", 575 Namespace: job.Namespace, 576 }, 577 } 578 579 // Fetch the response 580 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 581 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 582 t.Fatalf("expected enforcement error") 583 } 584 585 // Reregister request and enforcing it be at the correct index 586 job.Priority = job.Priority + 1 587 req = &structs.JobRegisterRequest{ 588 Job: job, 589 EnforceIndex: true, 590 JobModifyIndex: curIndex, 591 WriteRequest: structs.WriteRequest{ 592 Region: "global", 593 Namespace: job.Namespace, 594 }, 595 } 596 597 // Fetch the response 598 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 599 t.Fatalf("err: %v", err) 600 } 601 if resp.Index == 0 { 602 t.Fatalf("bad index: %d", resp.Index) 603 } 604 605 out, err = state.JobByID(ws, job.Namespace, job.ID) 606 if err != nil { 607 t.Fatalf("err: %v", err) 608 } 609 if out == nil { 610 t.Fatalf("expected job") 611 } 612 if out.Priority != job.Priority { 613 t.Fatalf("priority mis-match") 614 } 615 } 616 617 func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) { 618 t.Parallel() 619 s1 := TestServer(t, func(c *Config) { 620 c.NumSchedulers = 0 // Prevent automatic dequeue 621 f := false 622 c.VaultConfig.Enabled = &f 623 }) 624 defer s1.Shutdown() 625 codec := rpcClient(t, s1) 626 testutil.WaitForLeader(t, s1.RPC) 627 628 // Create the register request with a job asking for a vault policy 629 job := mock.Job() 630 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 631 Policies: []string{"foo"}, 632 ChangeMode: structs.VaultChangeModeRestart, 633 } 634 req := &structs.JobRegisterRequest{ 635 Job: job, 636 WriteRequest: structs.WriteRequest{ 637 Region: "global", 638 Namespace: job.Namespace, 639 }, 640 } 641 642 // Fetch the response 643 var resp structs.JobRegisterResponse 644 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 645 if err == nil || !strings.Contains(err.Error(), "Vault not enabled") { 646 t.Fatalf("expected Vault not enabled error: %v", err) 647 } 648 } 649 650 func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) { 651 t.Parallel() 652 s1 := TestServer(t, func(c *Config) { 653 c.NumSchedulers = 0 // Prevent automatic dequeue 654 }) 655 defer s1.Shutdown() 656 codec := rpcClient(t, s1) 657 testutil.WaitForLeader(t, s1.RPC) 658 659 // Enable vault and allow authenticated 660 tr := true 661 s1.config.VaultConfig.Enabled = &tr 662 s1.config.VaultConfig.AllowUnauthenticated = &tr 663 664 // Replace the Vault Client on the server 665 s1.vault = &TestVaultClient{} 666 667 // Create the register request with a job asking for a vault policy 668 job := mock.Job() 669 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 670 Policies: []string{"foo"}, 671 ChangeMode: structs.VaultChangeModeRestart, 672 } 673 req := &structs.JobRegisterRequest{ 674 Job: job, 675 WriteRequest: structs.WriteRequest{ 676 Region: "global", 677 Namespace: job.Namespace, 678 }, 679 } 680 681 // Fetch the response 682 var resp structs.JobRegisterResponse 683 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 684 if err != nil { 685 t.Fatalf("bad: %v", err) 686 } 687 688 // Check for the job in the FSM 689 state := s1.fsm.State() 690 ws := memdb.NewWatchSet() 691 out, err := state.JobByID(ws, job.Namespace, job.ID) 692 if err != nil { 693 t.Fatalf("err: %v", err) 694 } 695 if out == nil { 696 t.Fatalf("expected job") 697 } 698 if out.CreateIndex != resp.JobModifyIndex { 699 t.Fatalf("index mis-match") 700 } 701 } 702 703 func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) { 704 t.Parallel() 705 s1 := TestServer(t, func(c *Config) { 706 c.NumSchedulers = 0 // Prevent automatic dequeue 707 }) 708 defer s1.Shutdown() 709 codec := rpcClient(t, s1) 710 testutil.WaitForLeader(t, s1.RPC) 711 712 // Enable vault 713 tr, f := true, false 714 s1.config.VaultConfig.Enabled = &tr 715 s1.config.VaultConfig.AllowUnauthenticated = &f 716 717 // Replace the Vault Client on the server 718 s1.vault = &TestVaultClient{} 719 720 // Create the register request with a job asking for a vault policy but 721 // don't send a Vault token 722 job := mock.Job() 723 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 724 Policies: []string{"foo"}, 725 ChangeMode: structs.VaultChangeModeRestart, 726 } 727 req := &structs.JobRegisterRequest{ 728 Job: job, 729 WriteRequest: structs.WriteRequest{ 730 Region: "global", 731 Namespace: job.Namespace, 732 }, 733 } 734 735 // Fetch the response 736 var resp structs.JobRegisterResponse 737 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 738 if err == nil || !strings.Contains(err.Error(), "missing Vault Token") { 739 t.Fatalf("expected Vault not enabled error: %v", err) 740 } 741 } 742 743 func TestJobEndpoint_Register_Vault_Policies(t *testing.T) { 744 t.Parallel() 745 s1 := TestServer(t, func(c *Config) { 746 c.NumSchedulers = 0 // Prevent automatic dequeue 747 }) 748 defer s1.Shutdown() 749 codec := rpcClient(t, s1) 750 testutil.WaitForLeader(t, s1.RPC) 751 752 // Enable vault 753 tr, f := true, false 754 s1.config.VaultConfig.Enabled = &tr 755 s1.config.VaultConfig.AllowUnauthenticated = &f 756 757 // Replace the Vault Client on the server 758 tvc := &TestVaultClient{} 759 s1.vault = tvc 760 761 // Add three tokens: one that allows the requesting policy, one that does 762 // not and one that returns an error 763 policy := "foo" 764 765 badToken := uuid.Generate() 766 badPolicies := []string{"a", "b", "c"} 767 tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies) 768 769 goodToken := uuid.Generate() 770 goodPolicies := []string{"foo", "bar", "baz"} 771 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 772 773 rootToken := uuid.Generate() 774 rootPolicies := []string{"root"} 775 tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies) 776 777 errToken := uuid.Generate() 778 expectedErr := fmt.Errorf("return errors from vault") 779 tvc.SetLookupTokenError(errToken, expectedErr) 780 781 // Create the register request with a job asking for a vault policy but 782 // send the bad Vault token 783 job := mock.Job() 784 job.VaultToken = badToken 785 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 786 Policies: []string{policy}, 787 ChangeMode: structs.VaultChangeModeRestart, 788 } 789 req := &structs.JobRegisterRequest{ 790 Job: job, 791 WriteRequest: structs.WriteRequest{ 792 Region: "global", 793 Namespace: job.Namespace, 794 }, 795 } 796 797 // Fetch the response 798 var resp structs.JobRegisterResponse 799 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 800 if err == nil || !strings.Contains(err.Error(), 801 "doesn't allow access to the following policies: "+policy) { 802 t.Fatalf("expected permission denied error: %v", err) 803 } 804 805 // Use the err token 806 job.VaultToken = errToken 807 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 808 if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) { 809 t.Fatalf("expected permission denied error: %v", err) 810 } 811 812 // Use the good token 813 job.VaultToken = goodToken 814 815 // Fetch the response 816 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 817 t.Fatalf("bad: %v", err) 818 } 819 820 // Check for the job in the FSM 821 state := s1.fsm.State() 822 ws := memdb.NewWatchSet() 823 out, err := state.JobByID(ws, job.Namespace, job.ID) 824 if err != nil { 825 t.Fatalf("err: %v", err) 826 } 827 if out == nil { 828 t.Fatalf("expected job") 829 } 830 if out.CreateIndex != resp.JobModifyIndex { 831 t.Fatalf("index mis-match") 832 } 833 if out.VaultToken != "" { 834 t.Fatalf("vault token not cleared") 835 } 836 837 // Check that an implicit constraint was created 838 constraints := out.TaskGroups[0].Constraints 839 if l := len(constraints); l != 1 { 840 t.Fatalf("Unexpected number of tests: %v", l) 841 } 842 843 if !constraints[0].Equal(vaultConstraint) { 844 t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint) 845 } 846 847 // Create the register request with another job asking for a vault policy but 848 // send the root Vault token 849 job2 := mock.Job() 850 job2.VaultToken = rootToken 851 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 852 Policies: []string{policy}, 853 ChangeMode: structs.VaultChangeModeRestart, 854 } 855 req = &structs.JobRegisterRequest{ 856 Job: job2, 857 WriteRequest: structs.WriteRequest{ 858 Region: "global", 859 Namespace: job.Namespace, 860 }, 861 } 862 863 // Fetch the response 864 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 865 t.Fatalf("bad: %v", err) 866 } 867 868 // Check for the job in the FSM 869 out, err = state.JobByID(ws, job2.Namespace, job2.ID) 870 if err != nil { 871 t.Fatalf("err: %v", err) 872 } 873 if out == nil { 874 t.Fatalf("expected job") 875 } 876 if out.CreateIndex != resp.JobModifyIndex { 877 t.Fatalf("index mis-match") 878 } 879 if out.VaultToken != "" { 880 t.Fatalf("vault token not cleared") 881 } 882 } 883 884 func TestJobEndpoint_Revert(t *testing.T) { 885 t.Parallel() 886 s1 := TestServer(t, func(c *Config) { 887 c.NumSchedulers = 0 // Prevent automatic dequeue 888 }) 889 defer s1.Shutdown() 890 codec := rpcClient(t, s1) 891 testutil.WaitForLeader(t, s1.RPC) 892 893 // Create the initial register request 894 job := mock.Job() 895 job.Priority = 100 896 req := &structs.JobRegisterRequest{ 897 Job: job, 898 WriteRequest: structs.WriteRequest{ 899 Region: "global", 900 Namespace: job.Namespace, 901 }, 902 } 903 904 // Fetch the response 905 var resp structs.JobRegisterResponse 906 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 907 t.Fatalf("err: %v", err) 908 } 909 if resp.Index == 0 { 910 t.Fatalf("bad index: %d", resp.Index) 911 } 912 913 // Reregister again to get another version 914 job2 := job.Copy() 915 job2.Priority = 1 916 req = &structs.JobRegisterRequest{ 917 Job: job2, 918 WriteRequest: structs.WriteRequest{ 919 Region: "global", 920 Namespace: job.Namespace, 921 }, 922 } 923 924 // Fetch the response 925 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 926 t.Fatalf("err: %v", err) 927 } 928 if resp.Index == 0 { 929 t.Fatalf("bad index: %d", resp.Index) 930 } 931 932 // Create revert request and enforcing it be at an incorrect version 933 revertReq := &structs.JobRevertRequest{ 934 JobID: job.ID, 935 JobVersion: 0, 936 EnforcePriorVersion: helper.Uint64ToPtr(10), 937 WriteRequest: structs.WriteRequest{ 938 Region: "global", 939 Namespace: job.Namespace, 940 }, 941 } 942 943 // Fetch the response 944 err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp) 945 if err == nil || !strings.Contains(err.Error(), "enforcing version 10") { 946 t.Fatalf("expected enforcement error") 947 } 948 949 // Create revert request and enforcing it be at the current version 950 revertReq = &structs.JobRevertRequest{ 951 JobID: job.ID, 952 JobVersion: 1, 953 WriteRequest: structs.WriteRequest{ 954 Region: "global", 955 Namespace: job.Namespace, 956 }, 957 } 958 959 // Fetch the response 960 err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp) 961 if err == nil || !strings.Contains(err.Error(), "current version") { 962 t.Fatalf("expected current version err: %v", err) 963 } 964 965 // Create revert request and enforcing it be at version 1 966 revertReq = &structs.JobRevertRequest{ 967 JobID: job.ID, 968 JobVersion: 0, 969 EnforcePriorVersion: helper.Uint64ToPtr(1), 970 WriteRequest: structs.WriteRequest{ 971 Region: "global", 972 Namespace: job.Namespace, 973 }, 974 } 975 976 // Fetch the response 977 if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil { 978 t.Fatalf("err: %v", err) 979 } 980 if resp.Index == 0 { 981 t.Fatalf("bad index: %d", resp.Index) 982 } 983 if resp.EvalID == "" || resp.EvalCreateIndex == 0 { 984 t.Fatalf("bad created eval: %+v", resp) 985 } 986 if resp.JobModifyIndex == 0 { 987 t.Fatalf("bad job modify index: %d", resp.JobModifyIndex) 988 } 989 990 // Create revert request and don't enforce. We are at version 2 but it is 991 // the same as version 0 992 revertReq = &structs.JobRevertRequest{ 993 JobID: job.ID, 994 JobVersion: 0, 995 WriteRequest: structs.WriteRequest{ 996 Region: "global", 997 Namespace: job.Namespace, 998 }, 999 } 1000 1001 // Fetch the response 1002 if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil { 1003 t.Fatalf("err: %v", err) 1004 } 1005 if resp.Index == 0 { 1006 t.Fatalf("bad index: %d", resp.Index) 1007 } 1008 if resp.EvalID == "" || resp.EvalCreateIndex == 0 { 1009 t.Fatalf("bad created eval: %+v", resp) 1010 } 1011 if resp.JobModifyIndex == 0 { 1012 t.Fatalf("bad job modify index: %d", resp.JobModifyIndex) 1013 } 1014 1015 // Check that the job is at the correct version and that the eval was 1016 // created 1017 state := s1.fsm.State() 1018 ws := memdb.NewWatchSet() 1019 out, err := state.JobByID(ws, job.Namespace, job.ID) 1020 if err != nil { 1021 t.Fatalf("err: %v", err) 1022 } 1023 if out == nil { 1024 t.Fatalf("expected job") 1025 } 1026 if out.Priority != job.Priority { 1027 t.Fatalf("priority mis-match") 1028 } 1029 if out.Version != 2 { 1030 t.Fatalf("got version %d; want %d", out.Version, 2) 1031 } 1032 1033 eout, err := state.EvalByID(ws, resp.EvalID) 1034 if err != nil { 1035 t.Fatalf("err: %v", err) 1036 } 1037 if eout == nil { 1038 t.Fatalf("expected eval") 1039 } 1040 if eout.JobID != job.ID { 1041 t.Fatalf("job id mis-match") 1042 } 1043 1044 versions, err := state.JobVersionsByID(ws, job.Namespace, job.ID) 1045 if err != nil { 1046 t.Fatalf("err: %v", err) 1047 } 1048 if len(versions) != 3 { 1049 t.Fatalf("got %d versions; want %d", len(versions), 3) 1050 } 1051 } 1052 1053 func TestJobEndpoint_Revert_ACL(t *testing.T) { 1054 t.Parallel() 1055 require := require.New(t) 1056 1057 s1, root := TestACLServer(t, func(c *Config) { 1058 c.NumSchedulers = 0 // Prevent automatic dequeue 1059 }) 1060 1061 defer s1.Shutdown() 1062 codec := rpcClient(t, s1) 1063 state := s1.fsm.State() 1064 testutil.WaitForLeader(t, s1.RPC) 1065 1066 // Create the job 1067 job := mock.Job() 1068 err := state.UpsertJob(300, job) 1069 require.Nil(err) 1070 1071 job2 := job.Copy() 1072 job2.Priority = 1 1073 err = state.UpsertJob(400, job2) 1074 require.Nil(err) 1075 1076 // Create revert request and enforcing it be at the current version 1077 revertReq := &structs.JobRevertRequest{ 1078 JobID: job.ID, 1079 JobVersion: 0, 1080 WriteRequest: structs.WriteRequest{ 1081 Region: "global", 1082 Namespace: job.Namespace, 1083 }, 1084 } 1085 1086 // Attempt to fetch the response without a valid token 1087 var resp structs.JobRegisterResponse 1088 err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp) 1089 require.NotNil(err) 1090 require.Contains(err.Error(), "Permission denied") 1091 1092 // Attempt to fetch the response with an invalid token 1093 invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", 1094 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 1095 1096 revertReq.AuthToken = invalidToken.SecretID 1097 var invalidResp structs.JobRegisterResponse 1098 err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &invalidResp) 1099 require.NotNil(err) 1100 require.Contains(err.Error(), "Permission denied") 1101 1102 // Fetch the response with a valid management token 1103 revertReq.AuthToken = root.SecretID 1104 var validResp structs.JobRegisterResponse 1105 err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &validResp) 1106 require.Nil(err) 1107 1108 // Try with a valid non-management token 1109 validToken := mock.CreatePolicyAndToken(t, state, 1003, "test-valid", 1110 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob})) 1111 1112 revertReq.AuthToken = validToken.SecretID 1113 var validResp2 structs.JobRegisterResponse 1114 err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &validResp2) 1115 require.Nil(err) 1116 } 1117 1118 func TestJobEndpoint_Stable(t *testing.T) { 1119 t.Parallel() 1120 s1 := TestServer(t, func(c *Config) { 1121 c.NumSchedulers = 0 // Prevent automatic dequeue 1122 }) 1123 defer s1.Shutdown() 1124 codec := rpcClient(t, s1) 1125 testutil.WaitForLeader(t, s1.RPC) 1126 1127 // Create the initial register request 1128 job := mock.Job() 1129 req := &structs.JobRegisterRequest{ 1130 Job: job, 1131 WriteRequest: structs.WriteRequest{ 1132 Region: "global", 1133 Namespace: job.Namespace, 1134 }, 1135 } 1136 1137 // Fetch the response 1138 var resp structs.JobRegisterResponse 1139 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1140 t.Fatalf("err: %v", err) 1141 } 1142 if resp.Index == 0 { 1143 t.Fatalf("bad index: %d", resp.Index) 1144 } 1145 1146 // Create stability request 1147 stableReq := &structs.JobStabilityRequest{ 1148 JobID: job.ID, 1149 JobVersion: 0, 1150 Stable: true, 1151 WriteRequest: structs.WriteRequest{ 1152 Region: "global", 1153 Namespace: job.Namespace, 1154 }, 1155 } 1156 1157 // Fetch the response 1158 var stableResp structs.JobStabilityResponse 1159 if err := msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &stableResp); err != nil { 1160 t.Fatalf("err: %v", err) 1161 } 1162 if stableResp.Index == 0 { 1163 t.Fatalf("bad index: %d", resp.Index) 1164 } 1165 1166 // Check that the job is marked stable 1167 state := s1.fsm.State() 1168 ws := memdb.NewWatchSet() 1169 out, err := state.JobByID(ws, job.Namespace, job.ID) 1170 if err != nil { 1171 t.Fatalf("err: %v", err) 1172 } 1173 if out == nil { 1174 t.Fatalf("expected job") 1175 } 1176 if !out.Stable { 1177 t.Fatalf("Job is not marked stable") 1178 } 1179 } 1180 1181 func TestJobEndpoint_Stable_ACL(t *testing.T) { 1182 t.Parallel() 1183 require := require.New(t) 1184 1185 s1, root := TestACLServer(t, func(c *Config) { 1186 c.NumSchedulers = 0 // Prevent automatic dequeue 1187 }) 1188 defer s1.Shutdown() 1189 codec := rpcClient(t, s1) 1190 state := s1.fsm.State() 1191 testutil.WaitForLeader(t, s1.RPC) 1192 1193 // Register the job 1194 job := mock.Job() 1195 err := state.UpsertJob(1000, job) 1196 require.Nil(err) 1197 1198 // Create stability request 1199 stableReq := &structs.JobStabilityRequest{ 1200 JobID: job.ID, 1201 JobVersion: 0, 1202 Stable: true, 1203 WriteRequest: structs.WriteRequest{ 1204 Region: "global", 1205 Namespace: job.Namespace, 1206 }, 1207 } 1208 1209 // Attempt to fetch the token without a token 1210 var stableResp structs.JobStabilityResponse 1211 err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &stableResp) 1212 require.NotNil(err) 1213 require.Contains("Permission denied", err.Error()) 1214 1215 // Expect failure for request with an invalid token 1216 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 1217 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 1218 1219 stableReq.AuthToken = invalidToken.SecretID 1220 var invalidStableResp structs.JobStabilityResponse 1221 err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &invalidStableResp) 1222 require.NotNil(err) 1223 require.Contains("Permission denied", err.Error()) 1224 1225 // Attempt to fetch with a management token 1226 stableReq.AuthToken = root.SecretID 1227 var validStableResp structs.JobStabilityResponse 1228 err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &validStableResp) 1229 require.Nil(err) 1230 1231 // Attempt to fetch with a valid token 1232 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-invalid", 1233 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob})) 1234 1235 stableReq.AuthToken = validToken.SecretID 1236 var validStableResp2 structs.JobStabilityResponse 1237 err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &validStableResp2) 1238 require.Nil(err) 1239 1240 // Check that the job is marked stable 1241 ws := memdb.NewWatchSet() 1242 out, err := state.JobByID(ws, job.Namespace, job.ID) 1243 require.Nil(err) 1244 require.NotNil(job) 1245 require.Equal(true, out.Stable) 1246 } 1247 1248 func TestJobEndpoint_Evaluate(t *testing.T) { 1249 t.Parallel() 1250 s1 := TestServer(t, func(c *Config) { 1251 c.NumSchedulers = 0 // Prevent automatic dequeue 1252 }) 1253 defer s1.Shutdown() 1254 codec := rpcClient(t, s1) 1255 testutil.WaitForLeader(t, s1.RPC) 1256 1257 // Create the register request 1258 job := mock.Job() 1259 req := &structs.JobRegisterRequest{ 1260 Job: job, 1261 WriteRequest: structs.WriteRequest{ 1262 Region: "global", 1263 Namespace: job.Namespace, 1264 }, 1265 } 1266 1267 // Fetch the response 1268 var resp structs.JobRegisterResponse 1269 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1270 t.Fatalf("err: %v", err) 1271 } 1272 if resp.Index == 0 { 1273 t.Fatalf("bad index: %d", resp.Index) 1274 } 1275 1276 // Force a re-evaluation 1277 reEval := &structs.JobEvaluateRequest{ 1278 JobID: job.ID, 1279 WriteRequest: structs.WriteRequest{ 1280 Region: "global", 1281 Namespace: job.Namespace, 1282 }, 1283 } 1284 1285 // Fetch the response 1286 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil { 1287 t.Fatalf("err: %v", err) 1288 } 1289 if resp.Index == 0 { 1290 t.Fatalf("bad index: %d", resp.Index) 1291 } 1292 1293 // Lookup the evaluation 1294 state := s1.fsm.State() 1295 ws := memdb.NewWatchSet() 1296 eval, err := state.EvalByID(ws, resp.EvalID) 1297 if err != nil { 1298 t.Fatalf("err: %v", err) 1299 } 1300 if eval == nil { 1301 t.Fatalf("expected eval") 1302 } 1303 if eval.CreateIndex != resp.EvalCreateIndex { 1304 t.Fatalf("index mis-match") 1305 } 1306 1307 if eval.Priority != job.Priority { 1308 t.Fatalf("bad: %#v", eval) 1309 } 1310 if eval.Type != job.Type { 1311 t.Fatalf("bad: %#v", eval) 1312 } 1313 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 1314 t.Fatalf("bad: %#v", eval) 1315 } 1316 if eval.JobID != job.ID { 1317 t.Fatalf("bad: %#v", eval) 1318 } 1319 if eval.JobModifyIndex != resp.JobModifyIndex { 1320 t.Fatalf("bad: %#v", eval) 1321 } 1322 if eval.Status != structs.EvalStatusPending { 1323 t.Fatalf("bad: %#v", eval) 1324 } 1325 } 1326 1327 func TestJobEndpoint_ForceRescheduleEvaluate(t *testing.T) { 1328 require := require.New(t) 1329 t.Parallel() 1330 s1 := TestServer(t, func(c *Config) { 1331 c.NumSchedulers = 0 // Prevent automatic dequeue 1332 }) 1333 defer s1.Shutdown() 1334 codec := rpcClient(t, s1) 1335 testutil.WaitForLeader(t, s1.RPC) 1336 1337 // Create the register request 1338 job := mock.Job() 1339 req := &structs.JobRegisterRequest{ 1340 Job: job, 1341 WriteRequest: structs.WriteRequest{ 1342 Region: "global", 1343 Namespace: job.Namespace, 1344 }, 1345 } 1346 1347 // Fetch the response 1348 var resp structs.JobRegisterResponse 1349 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 1350 require.Nil(err) 1351 require.NotEqual(0, resp.Index) 1352 1353 state := s1.fsm.State() 1354 job, err = state.JobByID(nil, structs.DefaultNamespace, job.ID) 1355 require.Nil(err) 1356 1357 // Create a failed alloc 1358 alloc := mock.Alloc() 1359 alloc.Job = job 1360 alloc.JobID = job.ID 1361 alloc.TaskGroup = job.TaskGroups[0].Name 1362 alloc.Namespace = job.Namespace 1363 alloc.ClientStatus = structs.AllocClientStatusFailed 1364 err = s1.State().UpsertAllocs(resp.Index+1, []*structs.Allocation{alloc}) 1365 require.Nil(err) 1366 1367 // Force a re-evaluation 1368 reEval := &structs.JobEvaluateRequest{ 1369 JobID: job.ID, 1370 EvalOptions: structs.EvalOptions{ForceReschedule: true}, 1371 WriteRequest: structs.WriteRequest{ 1372 Region: "global", 1373 Namespace: job.Namespace, 1374 }, 1375 } 1376 1377 // Fetch the response 1378 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp) 1379 require.Nil(err) 1380 require.NotEqual(0, resp.Index) 1381 1382 // Lookup the evaluation 1383 ws := memdb.NewWatchSet() 1384 eval, err := state.EvalByID(ws, resp.EvalID) 1385 require.Nil(err) 1386 require.NotNil(eval) 1387 require.Equal(eval.CreateIndex, resp.EvalCreateIndex) 1388 require.Equal(eval.Priority, job.Priority) 1389 require.Equal(eval.Type, job.Type) 1390 require.Equal(eval.TriggeredBy, structs.EvalTriggerJobRegister) 1391 require.Equal(eval.JobID, job.ID) 1392 require.Equal(eval.JobModifyIndex, resp.JobModifyIndex) 1393 require.Equal(eval.Status, structs.EvalStatusPending) 1394 1395 // Lookup the alloc, verify DesiredTransition ForceReschedule 1396 alloc, err = state.AllocByID(ws, alloc.ID) 1397 require.NotNil(alloc) 1398 require.Nil(err) 1399 require.True(*alloc.DesiredTransition.ForceReschedule) 1400 } 1401 1402 func TestJobEndpoint_Evaluate_ACL(t *testing.T) { 1403 t.Parallel() 1404 require := require.New(t) 1405 1406 s1, root := TestACLServer(t, func(c *Config) { 1407 c.NumSchedulers = 0 // Prevent automatic dequeue 1408 }) 1409 defer s1.Shutdown() 1410 codec := rpcClient(t, s1) 1411 testutil.WaitForLeader(t, s1.RPC) 1412 state := s1.fsm.State() 1413 1414 // Create the job 1415 job := mock.Job() 1416 err := state.UpsertJob(300, job) 1417 require.Nil(err) 1418 1419 // Force a re-evaluation 1420 reEval := &structs.JobEvaluateRequest{ 1421 JobID: job.ID, 1422 WriteRequest: structs.WriteRequest{ 1423 Region: "global", 1424 Namespace: job.Namespace, 1425 }, 1426 } 1427 1428 // Attempt to fetch the response without a token 1429 var resp structs.JobRegisterResponse 1430 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp) 1431 require.NotNil(err) 1432 require.Contains(err.Error(), "Permission denied") 1433 1434 // Attempt to fetch the response with an invalid token 1435 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 1436 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 1437 1438 reEval.AuthToken = invalidToken.SecretID 1439 var invalidResp structs.JobRegisterResponse 1440 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &invalidResp) 1441 require.NotNil(err) 1442 require.Contains(err.Error(), "Permission denied") 1443 1444 // Fetch the response with a valid management token 1445 reEval.AuthToken = root.SecretID 1446 var validResp structs.JobRegisterResponse 1447 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &validResp) 1448 require.Nil(err) 1449 1450 // Fetch the response with a valid token 1451 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 1452 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 1453 1454 reEval.AuthToken = validToken.SecretID 1455 var validResp2 structs.JobRegisterResponse 1456 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &validResp2) 1457 require.Nil(err) 1458 1459 // Lookup the evaluation 1460 ws := memdb.NewWatchSet() 1461 eval, err := state.EvalByID(ws, validResp2.EvalID) 1462 require.Nil(err) 1463 require.NotNil(eval) 1464 1465 require.Equal(eval.CreateIndex, validResp2.EvalCreateIndex) 1466 require.Equal(eval.Priority, job.Priority) 1467 require.Equal(eval.Type, job.Type) 1468 require.Equal(eval.TriggeredBy, structs.EvalTriggerJobRegister) 1469 require.Equal(eval.JobID, job.ID) 1470 require.Equal(eval.JobModifyIndex, validResp2.JobModifyIndex) 1471 require.Equal(eval.Status, structs.EvalStatusPending) 1472 } 1473 1474 func TestJobEndpoint_Evaluate_Periodic(t *testing.T) { 1475 t.Parallel() 1476 s1 := TestServer(t, func(c *Config) { 1477 c.NumSchedulers = 0 // Prevent automatic dequeue 1478 }) 1479 defer s1.Shutdown() 1480 codec := rpcClient(t, s1) 1481 testutil.WaitForLeader(t, s1.RPC) 1482 1483 // Create the register request 1484 job := mock.PeriodicJob() 1485 req := &structs.JobRegisterRequest{ 1486 Job: job, 1487 WriteRequest: structs.WriteRequest{ 1488 Region: "global", 1489 Namespace: job.Namespace, 1490 }, 1491 } 1492 1493 // Fetch the response 1494 var resp structs.JobRegisterResponse 1495 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1496 t.Fatalf("err: %v", err) 1497 } 1498 if resp.JobModifyIndex == 0 { 1499 t.Fatalf("bad index: %d", resp.Index) 1500 } 1501 1502 // Force a re-evaluation 1503 reEval := &structs.JobEvaluateRequest{ 1504 JobID: job.ID, 1505 WriteRequest: structs.WriteRequest{ 1506 Region: "global", 1507 Namespace: job.Namespace, 1508 }, 1509 } 1510 1511 // Fetch the response 1512 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 1513 t.Fatal("expect an err") 1514 } 1515 } 1516 1517 func TestJobEndpoint_Evaluate_ParameterizedJob(t *testing.T) { 1518 t.Parallel() 1519 s1 := TestServer(t, func(c *Config) { 1520 c.NumSchedulers = 0 // Prevent automatic dequeue 1521 }) 1522 defer s1.Shutdown() 1523 codec := rpcClient(t, s1) 1524 testutil.WaitForLeader(t, s1.RPC) 1525 1526 // Create the register request 1527 job := mock.BatchJob() 1528 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1529 req := &structs.JobRegisterRequest{ 1530 Job: job, 1531 WriteRequest: structs.WriteRequest{ 1532 Region: "global", 1533 Namespace: job.Namespace, 1534 }, 1535 } 1536 1537 // Fetch the response 1538 var resp structs.JobRegisterResponse 1539 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1540 t.Fatalf("err: %v", err) 1541 } 1542 if resp.JobModifyIndex == 0 { 1543 t.Fatalf("bad index: %d", resp.Index) 1544 } 1545 1546 // Force a re-evaluation 1547 reEval := &structs.JobEvaluateRequest{ 1548 JobID: job.ID, 1549 WriteRequest: structs.WriteRequest{ 1550 Region: "global", 1551 Namespace: job.Namespace, 1552 }, 1553 } 1554 1555 // Fetch the response 1556 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 1557 t.Fatal("expect an err") 1558 } 1559 } 1560 1561 func TestJobEndpoint_Deregister(t *testing.T) { 1562 t.Parallel() 1563 require := require.New(t) 1564 s1 := TestServer(t, func(c *Config) { 1565 c.NumSchedulers = 0 // Prevent automatic dequeue 1566 }) 1567 defer s1.Shutdown() 1568 codec := rpcClient(t, s1) 1569 testutil.WaitForLeader(t, s1.RPC) 1570 1571 // Create the register requests 1572 job := mock.Job() 1573 reg := &structs.JobRegisterRequest{ 1574 Job: job, 1575 WriteRequest: structs.WriteRequest{ 1576 Region: "global", 1577 Namespace: job.Namespace, 1578 }, 1579 } 1580 1581 // Fetch the response 1582 var resp structs.JobRegisterResponse 1583 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp)) 1584 1585 // Deregister but don't purge 1586 dereg := &structs.JobDeregisterRequest{ 1587 JobID: job.ID, 1588 Purge: false, 1589 WriteRequest: structs.WriteRequest{ 1590 Region: "global", 1591 Namespace: job.Namespace, 1592 }, 1593 } 1594 var resp2 structs.JobDeregisterResponse 1595 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2)) 1596 require.NotZero(resp2.Index) 1597 1598 // Check for the job in the FSM 1599 state := s1.fsm.State() 1600 out, err := state.JobByID(nil, job.Namespace, job.ID) 1601 require.Nil(err) 1602 require.NotNil(out) 1603 require.True(out.Stop) 1604 1605 // Lookup the evaluation 1606 eval, err := state.EvalByID(nil, resp2.EvalID) 1607 require.Nil(err) 1608 require.NotNil(eval) 1609 require.EqualValues(resp2.EvalCreateIndex, eval.CreateIndex) 1610 require.Equal(job.Priority, eval.Priority) 1611 require.Equal(job.Type, eval.Type) 1612 require.Equal(structs.EvalTriggerJobDeregister, eval.TriggeredBy) 1613 require.Equal(job.ID, eval.JobID) 1614 require.Equal(structs.EvalStatusPending, eval.Status) 1615 1616 // Deregister and purge 1617 dereg2 := &structs.JobDeregisterRequest{ 1618 JobID: job.ID, 1619 Purge: true, 1620 WriteRequest: structs.WriteRequest{ 1621 Region: "global", 1622 Namespace: job.Namespace, 1623 }, 1624 } 1625 var resp3 structs.JobDeregisterResponse 1626 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg2, &resp3)) 1627 require.NotZero(resp3.Index) 1628 1629 // Check for the job in the FSM 1630 out, err = state.JobByID(nil, job.Namespace, job.ID) 1631 require.Nil(err) 1632 require.Nil(out) 1633 1634 // Lookup the evaluation 1635 eval, err = state.EvalByID(nil, resp3.EvalID) 1636 require.Nil(err) 1637 require.NotNil(eval) 1638 1639 require.EqualValues(resp3.EvalCreateIndex, eval.CreateIndex) 1640 require.Equal(job.Priority, eval.Priority) 1641 require.Equal(job.Type, eval.Type) 1642 require.Equal(structs.EvalTriggerJobDeregister, eval.TriggeredBy) 1643 require.Equal(job.ID, eval.JobID) 1644 require.Equal(structs.EvalStatusPending, eval.Status) 1645 } 1646 1647 func TestJobEndpoint_Deregister_ACL(t *testing.T) { 1648 t.Parallel() 1649 require := require.New(t) 1650 1651 s1, root := TestACLServer(t, func(c *Config) { 1652 c.NumSchedulers = 0 // Prevent automatic dequeue 1653 }) 1654 defer s1.Shutdown() 1655 codec := rpcClient(t, s1) 1656 testutil.WaitForLeader(t, s1.RPC) 1657 state := s1.fsm.State() 1658 1659 // Create and register a job 1660 job := mock.Job() 1661 err := state.UpsertJob(100, job) 1662 1663 // Deregister and purge 1664 req := &structs.JobDeregisterRequest{ 1665 JobID: job.ID, 1666 Purge: true, 1667 WriteRequest: structs.WriteRequest{ 1668 Region: "global", 1669 Namespace: job.Namespace, 1670 }, 1671 } 1672 1673 // Expect failure for request without a token 1674 var resp structs.JobDeregisterResponse 1675 err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &resp) 1676 require.NotNil(err) 1677 require.Contains(err.Error(), "Permission denied") 1678 1679 // Expect failure for request with an invalid token 1680 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 1681 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 1682 req.AuthToken = invalidToken.SecretID 1683 1684 var invalidResp structs.JobDeregisterResponse 1685 err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &invalidResp) 1686 require.NotNil(err) 1687 require.Contains(err.Error(), "Permission denied") 1688 1689 // Expect success with a valid management token 1690 req.AuthToken = root.SecretID 1691 1692 var validResp structs.JobDeregisterResponse 1693 err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &validResp) 1694 require.Nil(err) 1695 require.NotEqual(validResp.Index, 0) 1696 1697 // Expect success with a valid token 1698 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 1699 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob})) 1700 req.AuthToken = validToken.SecretID 1701 1702 var validResp2 structs.JobDeregisterResponse 1703 err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &validResp2) 1704 require.Nil(err) 1705 require.NotEqual(validResp2.Index, 0) 1706 1707 // Check for the job in the FSM 1708 out, err := state.JobByID(nil, job.Namespace, job.ID) 1709 require.Nil(err) 1710 require.Nil(out) 1711 1712 // Lookup the evaluation 1713 eval, err := state.EvalByID(nil, validResp2.EvalID) 1714 require.Nil(err) 1715 require.NotNil(eval, nil) 1716 1717 require.Equal(eval.CreateIndex, validResp2.EvalCreateIndex) 1718 require.Equal(eval.Priority, structs.JobDefaultPriority) 1719 require.Equal(eval.Type, structs.JobTypeService) 1720 require.Equal(eval.TriggeredBy, structs.EvalTriggerJobDeregister) 1721 require.Equal(eval.JobID, job.ID) 1722 require.Equal(eval.JobModifyIndex, validResp2.JobModifyIndex) 1723 require.Equal(eval.Status, structs.EvalStatusPending) 1724 } 1725 1726 func TestJobEndpoint_Deregister_Nonexistent(t *testing.T) { 1727 t.Parallel() 1728 s1 := TestServer(t, func(c *Config) { 1729 c.NumSchedulers = 0 // Prevent automatic dequeue 1730 }) 1731 defer s1.Shutdown() 1732 codec := rpcClient(t, s1) 1733 testutil.WaitForLeader(t, s1.RPC) 1734 1735 // Deregister 1736 jobID := "foo" 1737 dereg := &structs.JobDeregisterRequest{ 1738 JobID: jobID, 1739 WriteRequest: structs.WriteRequest{ 1740 Region: "global", 1741 Namespace: structs.DefaultNamespace, 1742 }, 1743 } 1744 var resp2 structs.JobDeregisterResponse 1745 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1746 t.Fatalf("err: %v", err) 1747 } 1748 if resp2.JobModifyIndex == 0 { 1749 t.Fatalf("bad index: %d", resp2.Index) 1750 } 1751 1752 // Lookup the evaluation 1753 state := s1.fsm.State() 1754 ws := memdb.NewWatchSet() 1755 eval, err := state.EvalByID(ws, resp2.EvalID) 1756 if err != nil { 1757 t.Fatalf("err: %v", err) 1758 } 1759 if eval == nil { 1760 t.Fatalf("expected eval") 1761 } 1762 if eval.CreateIndex != resp2.EvalCreateIndex { 1763 t.Fatalf("index mis-match") 1764 } 1765 1766 if eval.Priority != structs.JobDefaultPriority { 1767 t.Fatalf("bad: %#v", eval) 1768 } 1769 if eval.Type != structs.JobTypeService { 1770 t.Fatalf("bad: %#v", eval) 1771 } 1772 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 1773 t.Fatalf("bad: %#v", eval) 1774 } 1775 if eval.JobID != jobID { 1776 t.Fatalf("bad: %#v", eval) 1777 } 1778 if eval.JobModifyIndex != resp2.JobModifyIndex { 1779 t.Fatalf("bad: %#v", eval) 1780 } 1781 if eval.Status != structs.EvalStatusPending { 1782 t.Fatalf("bad: %#v", eval) 1783 } 1784 } 1785 1786 func TestJobEndpoint_Deregister_Periodic(t *testing.T) { 1787 t.Parallel() 1788 s1 := TestServer(t, func(c *Config) { 1789 c.NumSchedulers = 0 // Prevent automatic dequeue 1790 }) 1791 defer s1.Shutdown() 1792 codec := rpcClient(t, s1) 1793 testutil.WaitForLeader(t, s1.RPC) 1794 1795 // Create the register request 1796 job := mock.PeriodicJob() 1797 reg := &structs.JobRegisterRequest{ 1798 Job: job, 1799 WriteRequest: structs.WriteRequest{ 1800 Region: "global", 1801 Namespace: job.Namespace, 1802 }, 1803 } 1804 1805 // Fetch the response 1806 var resp structs.JobRegisterResponse 1807 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1808 t.Fatalf("err: %v", err) 1809 } 1810 1811 // Deregister 1812 dereg := &structs.JobDeregisterRequest{ 1813 JobID: job.ID, 1814 Purge: true, 1815 WriteRequest: structs.WriteRequest{ 1816 Region: "global", 1817 Namespace: job.Namespace, 1818 }, 1819 } 1820 var resp2 structs.JobDeregisterResponse 1821 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1822 t.Fatalf("err: %v", err) 1823 } 1824 if resp2.JobModifyIndex == 0 { 1825 t.Fatalf("bad index: %d", resp2.Index) 1826 } 1827 1828 // Check for the node in the FSM 1829 state := s1.fsm.State() 1830 ws := memdb.NewWatchSet() 1831 out, err := state.JobByID(ws, job.Namespace, job.ID) 1832 if err != nil { 1833 t.Fatalf("err: %v", err) 1834 } 1835 if out != nil { 1836 t.Fatalf("unexpected job") 1837 } 1838 1839 if resp.EvalID != "" { 1840 t.Fatalf("Deregister created an eval for a periodic job") 1841 } 1842 } 1843 1844 func TestJobEndpoint_Deregister_ParameterizedJob(t *testing.T) { 1845 t.Parallel() 1846 s1 := TestServer(t, func(c *Config) { 1847 c.NumSchedulers = 0 // Prevent automatic dequeue 1848 }) 1849 defer s1.Shutdown() 1850 codec := rpcClient(t, s1) 1851 testutil.WaitForLeader(t, s1.RPC) 1852 1853 // Create the register request 1854 job := mock.BatchJob() 1855 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1856 reg := &structs.JobRegisterRequest{ 1857 Job: job, 1858 WriteRequest: structs.WriteRequest{ 1859 Region: "global", 1860 Namespace: job.Namespace, 1861 }, 1862 } 1863 1864 // Fetch the response 1865 var resp structs.JobRegisterResponse 1866 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1867 t.Fatalf("err: %v", err) 1868 } 1869 1870 // Deregister 1871 dereg := &structs.JobDeregisterRequest{ 1872 JobID: job.ID, 1873 Purge: true, 1874 WriteRequest: structs.WriteRequest{ 1875 Region: "global", 1876 Namespace: job.Namespace, 1877 }, 1878 } 1879 var resp2 structs.JobDeregisterResponse 1880 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1881 t.Fatalf("err: %v", err) 1882 } 1883 if resp2.JobModifyIndex == 0 { 1884 t.Fatalf("bad index: %d", resp2.Index) 1885 } 1886 1887 // Check for the node in the FSM 1888 state := s1.fsm.State() 1889 ws := memdb.NewWatchSet() 1890 out, err := state.JobByID(ws, job.Namespace, job.ID) 1891 if err != nil { 1892 t.Fatalf("err: %v", err) 1893 } 1894 if out != nil { 1895 t.Fatalf("unexpected job") 1896 } 1897 1898 if resp.EvalID != "" { 1899 t.Fatalf("Deregister created an eval for a parameterized job") 1900 } 1901 } 1902 1903 func TestJobEndpoint_BatchDeregister(t *testing.T) { 1904 t.Parallel() 1905 require := require.New(t) 1906 s1 := TestServer(t, func(c *Config) { 1907 c.NumSchedulers = 0 // Prevent automatic dequeue 1908 }) 1909 defer s1.Shutdown() 1910 codec := rpcClient(t, s1) 1911 testutil.WaitForLeader(t, s1.RPC) 1912 1913 // Create the register requests 1914 job := mock.Job() 1915 reg := &structs.JobRegisterRequest{ 1916 Job: job, 1917 WriteRequest: structs.WriteRequest{ 1918 Region: "global", 1919 Namespace: job.Namespace, 1920 }, 1921 } 1922 1923 // Fetch the response 1924 var resp structs.JobRegisterResponse 1925 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp)) 1926 1927 job2 := mock.Job() 1928 job2.Priority = 1 1929 reg2 := &structs.JobRegisterRequest{ 1930 Job: job2, 1931 WriteRequest: structs.WriteRequest{ 1932 Region: "global", 1933 Namespace: job2.Namespace, 1934 }, 1935 } 1936 1937 // Fetch the response 1938 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Register", reg2, &resp)) 1939 1940 // Deregister 1941 dereg := &structs.JobBatchDeregisterRequest{ 1942 Jobs: map[structs.NamespacedID]*structs.JobDeregisterOptions{ 1943 { 1944 ID: job.ID, 1945 Namespace: job.Namespace, 1946 }: {}, 1947 { 1948 ID: job2.ID, 1949 Namespace: job2.Namespace, 1950 }: { 1951 Purge: true, 1952 }, 1953 }, 1954 WriteRequest: structs.WriteRequest{ 1955 Region: "global", 1956 Namespace: job.Namespace, 1957 }, 1958 } 1959 var resp2 structs.JobBatchDeregisterResponse 1960 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", dereg, &resp2)) 1961 require.NotZero(resp2.Index) 1962 1963 // Check for the job in the FSM 1964 state := s1.fsm.State() 1965 out, err := state.JobByID(nil, job.Namespace, job.ID) 1966 require.Nil(err) 1967 require.NotNil(out) 1968 require.True(out.Stop) 1969 1970 out, err = state.JobByID(nil, job2.Namespace, job2.ID) 1971 require.Nil(err) 1972 require.Nil(out) 1973 1974 // Lookup the evaluation 1975 for jobNS, eval := range resp2.JobEvals { 1976 expectedJob := job 1977 if jobNS.ID != job.ID { 1978 expectedJob = job2 1979 } 1980 1981 eval, err := state.EvalByID(nil, eval) 1982 require.Nil(err) 1983 require.NotNil(eval) 1984 require.EqualValues(resp2.Index, eval.CreateIndex) 1985 require.Equal(expectedJob.Priority, eval.Priority) 1986 require.Equal(expectedJob.Type, eval.Type) 1987 require.Equal(structs.EvalTriggerJobDeregister, eval.TriggeredBy) 1988 require.Equal(expectedJob.ID, eval.JobID) 1989 require.Equal(structs.EvalStatusPending, eval.Status) 1990 } 1991 } 1992 1993 func TestJobEndpoint_BatchDeregister_ACL(t *testing.T) { 1994 t.Parallel() 1995 require := require.New(t) 1996 1997 s1, root := TestACLServer(t, func(c *Config) { 1998 c.NumSchedulers = 0 // Prevent automatic dequeue 1999 }) 2000 defer s1.Shutdown() 2001 codec := rpcClient(t, s1) 2002 testutil.WaitForLeader(t, s1.RPC) 2003 state := s1.fsm.State() 2004 2005 // Create and register a job 2006 job, job2 := mock.Job(), mock.Job() 2007 require.Nil(state.UpsertJob(100, job)) 2008 require.Nil(state.UpsertJob(101, job2)) 2009 2010 // Deregister 2011 req := &structs.JobBatchDeregisterRequest{ 2012 Jobs: map[structs.NamespacedID]*structs.JobDeregisterOptions{ 2013 { 2014 ID: job.ID, 2015 Namespace: job.Namespace, 2016 }: {}, 2017 { 2018 ID: job2.ID, 2019 Namespace: job2.Namespace, 2020 }: {}, 2021 }, 2022 WriteRequest: structs.WriteRequest{ 2023 Region: "global", 2024 }, 2025 } 2026 2027 // Expect failure for request without a token 2028 var resp structs.JobBatchDeregisterResponse 2029 err := msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &resp) 2030 require.NotNil(err) 2031 require.True(structs.IsErrPermissionDenied(err)) 2032 2033 // Expect failure for request with an invalid token 2034 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 2035 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 2036 req.AuthToken = invalidToken.SecretID 2037 2038 var invalidResp structs.JobDeregisterResponse 2039 err = msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &invalidResp) 2040 require.NotNil(err) 2041 require.True(structs.IsErrPermissionDenied(err)) 2042 2043 // Expect success with a valid management token 2044 req.AuthToken = root.SecretID 2045 2046 var validResp structs.JobDeregisterResponse 2047 err = msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &validResp) 2048 require.Nil(err) 2049 require.NotEqual(validResp.Index, 0) 2050 2051 // Expect success with a valid token 2052 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 2053 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob})) 2054 req.AuthToken = validToken.SecretID 2055 2056 var validResp2 structs.JobDeregisterResponse 2057 err = msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &validResp2) 2058 require.Nil(err) 2059 require.NotEqual(validResp2.Index, 0) 2060 } 2061 2062 func TestJobEndpoint_GetJob(t *testing.T) { 2063 t.Parallel() 2064 s1 := TestServer(t, nil) 2065 defer s1.Shutdown() 2066 codec := rpcClient(t, s1) 2067 testutil.WaitForLeader(t, s1.RPC) 2068 2069 // Create the register request 2070 job := mock.Job() 2071 reg := &structs.JobRegisterRequest{ 2072 Job: job, 2073 WriteRequest: structs.WriteRequest{ 2074 Region: "global", 2075 Namespace: job.Namespace, 2076 }, 2077 } 2078 2079 // Fetch the response 2080 var resp structs.JobRegisterResponse 2081 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2082 t.Fatalf("err: %v", err) 2083 } 2084 job.CreateIndex = resp.JobModifyIndex 2085 job.ModifyIndex = resp.JobModifyIndex 2086 job.JobModifyIndex = resp.JobModifyIndex 2087 2088 // Lookup the job 2089 get := &structs.JobSpecificRequest{ 2090 JobID: job.ID, 2091 QueryOptions: structs.QueryOptions{ 2092 Region: "global", 2093 Namespace: job.Namespace, 2094 }, 2095 } 2096 var resp2 structs.SingleJobResponse 2097 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 2098 t.Fatalf("err: %v", err) 2099 } 2100 if resp2.Index != resp.JobModifyIndex { 2101 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 2102 } 2103 2104 // Make a copy of the origin job and change the service name so that we can 2105 // do a deep equal with the response from the GET JOB Api 2106 j := job 2107 j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend" 2108 for tgix, tg := range j.TaskGroups { 2109 for tidx, t := range tg.Tasks { 2110 for sidx, service := range t.Services { 2111 for cidx, check := range service.Checks { 2112 check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name 2113 } 2114 } 2115 } 2116 } 2117 2118 // Clear the submit times 2119 j.SubmitTime = 0 2120 resp2.Job.SubmitTime = 0 2121 2122 if !reflect.DeepEqual(j, resp2.Job) { 2123 t.Fatalf("bad: %#v %#v", job, resp2.Job) 2124 } 2125 2126 // Lookup non-existing job 2127 get.JobID = "foobarbaz" 2128 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 2129 t.Fatalf("err: %v", err) 2130 } 2131 if resp2.Index != resp.JobModifyIndex { 2132 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 2133 } 2134 if resp2.Job != nil { 2135 t.Fatalf("unexpected job") 2136 } 2137 } 2138 2139 func TestJobEndpoint_GetJob_ACL(t *testing.T) { 2140 t.Parallel() 2141 require := require.New(t) 2142 2143 s1, root := TestACLServer(t, nil) 2144 defer s1.Shutdown() 2145 codec := rpcClient(t, s1) 2146 testutil.WaitForLeader(t, s1.RPC) 2147 state := s1.fsm.State() 2148 2149 // Create the job 2150 job := mock.Job() 2151 err := state.UpsertJob(1000, job) 2152 require.Nil(err) 2153 2154 // Lookup the job 2155 get := &structs.JobSpecificRequest{ 2156 JobID: job.ID, 2157 QueryOptions: structs.QueryOptions{ 2158 Region: "global", 2159 Namespace: job.Namespace, 2160 }, 2161 } 2162 2163 // Looking up the job without a token should fail 2164 var resp structs.SingleJobResponse 2165 err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp) 2166 require.NotNil(err) 2167 require.Contains(err.Error(), "Permission denied") 2168 2169 // Expect failure for request with an invalid token 2170 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 2171 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 2172 2173 get.AuthToken = invalidToken.SecretID 2174 var invalidResp structs.SingleJobResponse 2175 err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &invalidResp) 2176 require.NotNil(err) 2177 require.Contains(err.Error(), "Permission denied") 2178 2179 // Looking up the job with a management token should succeed 2180 get.AuthToken = root.SecretID 2181 var validResp structs.SingleJobResponse 2182 err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &validResp) 2183 require.Nil(err) 2184 require.Equal(job.ID, validResp.Job.ID) 2185 2186 // Looking up the job with a valid token should succeed 2187 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 2188 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 2189 2190 get.AuthToken = validToken.SecretID 2191 var validResp2 structs.SingleJobResponse 2192 err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &validResp2) 2193 require.Nil(err) 2194 require.Equal(job.ID, validResp2.Job.ID) 2195 } 2196 2197 func TestJobEndpoint_GetJob_Blocking(t *testing.T) { 2198 t.Parallel() 2199 s1 := TestServer(t, nil) 2200 defer s1.Shutdown() 2201 state := s1.fsm.State() 2202 codec := rpcClient(t, s1) 2203 testutil.WaitForLeader(t, s1.RPC) 2204 2205 // Create the jobs 2206 job1 := mock.Job() 2207 job2 := mock.Job() 2208 2209 // Upsert a job we are not interested in first. 2210 time.AfterFunc(100*time.Millisecond, func() { 2211 if err := state.UpsertJob(100, job1); err != nil { 2212 t.Fatalf("err: %v", err) 2213 } 2214 }) 2215 2216 // Upsert another job later which should trigger the watch. 2217 time.AfterFunc(200*time.Millisecond, func() { 2218 if err := state.UpsertJob(200, job2); err != nil { 2219 t.Fatalf("err: %v", err) 2220 } 2221 }) 2222 2223 req := &structs.JobSpecificRequest{ 2224 JobID: job2.ID, 2225 QueryOptions: structs.QueryOptions{ 2226 Region: "global", 2227 Namespace: job2.Namespace, 2228 MinQueryIndex: 150, 2229 }, 2230 } 2231 start := time.Now() 2232 var resp structs.SingleJobResponse 2233 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil { 2234 t.Fatalf("err: %v", err) 2235 } 2236 2237 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2238 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2239 } 2240 if resp.Index != 200 { 2241 t.Fatalf("Bad index: %d %d", resp.Index, 200) 2242 } 2243 if resp.Job == nil || resp.Job.ID != job2.ID { 2244 t.Fatalf("bad: %#v", resp.Job) 2245 } 2246 2247 // Job delete fires watches 2248 time.AfterFunc(100*time.Millisecond, func() { 2249 if err := state.DeleteJob(300, job2.Namespace, job2.ID); err != nil { 2250 t.Fatalf("err: %v", err) 2251 } 2252 }) 2253 2254 req.QueryOptions.MinQueryIndex = 250 2255 start = time.Now() 2256 2257 var resp2 structs.SingleJobResponse 2258 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil { 2259 t.Fatalf("err: %v", err) 2260 } 2261 2262 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 2263 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 2264 } 2265 if resp2.Index != 300 { 2266 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 2267 } 2268 if resp2.Job != nil { 2269 t.Fatalf("bad: %#v", resp2.Job) 2270 } 2271 } 2272 2273 func TestJobEndpoint_GetJobVersions(t *testing.T) { 2274 t.Parallel() 2275 s1 := TestServer(t, nil) 2276 defer s1.Shutdown() 2277 codec := rpcClient(t, s1) 2278 testutil.WaitForLeader(t, s1.RPC) 2279 2280 // Create the register request 2281 job := mock.Job() 2282 job.Priority = 88 2283 reg := &structs.JobRegisterRequest{ 2284 Job: job, 2285 WriteRequest: structs.WriteRequest{ 2286 Region: "global", 2287 Namespace: job.Namespace, 2288 }, 2289 } 2290 2291 // Fetch the response 2292 var resp structs.JobRegisterResponse 2293 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2294 t.Fatalf("err: %v", err) 2295 } 2296 2297 // Register the job again to create another version 2298 job.Priority = 100 2299 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2300 t.Fatalf("err: %v", err) 2301 } 2302 2303 // Lookup the job 2304 get := &structs.JobVersionsRequest{ 2305 JobID: job.ID, 2306 QueryOptions: structs.QueryOptions{ 2307 Region: "global", 2308 Namespace: job.Namespace, 2309 }, 2310 } 2311 var versionsResp structs.JobVersionsResponse 2312 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil { 2313 t.Fatalf("err: %v", err) 2314 } 2315 if versionsResp.Index != resp.JobModifyIndex { 2316 t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index) 2317 } 2318 2319 // Make sure there are two job versions 2320 versions := versionsResp.Versions 2321 if l := len(versions); l != 2 { 2322 t.Fatalf("Got %d versions; want 2", l) 2323 } 2324 2325 if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 1 { 2326 t.Fatalf("bad: %+v", v) 2327 } 2328 if v := versions[1]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 { 2329 t.Fatalf("bad: %+v", v) 2330 } 2331 2332 // Lookup non-existing job 2333 get.JobID = "foobarbaz" 2334 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil { 2335 t.Fatalf("err: %v", err) 2336 } 2337 if versionsResp.Index != resp.JobModifyIndex { 2338 t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index) 2339 } 2340 if l := len(versionsResp.Versions); l != 0 { 2341 t.Fatalf("unexpected versions: %d", l) 2342 } 2343 } 2344 2345 func TestJobEndpoint_GetJobVersions_ACL(t *testing.T) { 2346 t.Parallel() 2347 require := require.New(t) 2348 2349 s1, root := TestACLServer(t, nil) 2350 defer s1.Shutdown() 2351 codec := rpcClient(t, s1) 2352 testutil.WaitForLeader(t, s1.RPC) 2353 state := s1.fsm.State() 2354 2355 // Create two versions of a job with different priorities 2356 job := mock.Job() 2357 job.Priority = 88 2358 err := state.UpsertJob(10, job) 2359 require.Nil(err) 2360 2361 job.Priority = 100 2362 err = state.UpsertJob(100, job) 2363 require.Nil(err) 2364 2365 // Lookup the job 2366 get := &structs.JobVersionsRequest{ 2367 JobID: job.ID, 2368 QueryOptions: structs.QueryOptions{ 2369 Region: "global", 2370 Namespace: job.Namespace, 2371 }, 2372 } 2373 2374 // Attempt to fetch without a token should fail 2375 var resp structs.JobVersionsResponse 2376 err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &resp) 2377 require.NotNil(err) 2378 require.Contains(err.Error(), "Permission denied") 2379 2380 // Expect failure for request with an invalid token 2381 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 2382 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 2383 2384 get.AuthToken = invalidToken.SecretID 2385 var invalidResp structs.JobVersionsResponse 2386 err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &invalidResp) 2387 require.NotNil(err) 2388 require.Contains(err.Error(), "Permission denied") 2389 2390 // Expect success for request with a valid management token 2391 get.AuthToken = root.SecretID 2392 var validResp structs.JobVersionsResponse 2393 err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &validResp) 2394 require.Nil(err) 2395 2396 // Expect success for request with a valid token 2397 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 2398 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 2399 2400 get.AuthToken = validToken.SecretID 2401 var validResp2 structs.JobVersionsResponse 2402 err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &validResp2) 2403 require.Nil(err) 2404 2405 // Make sure there are two job versions 2406 versions := validResp2.Versions 2407 require.Equal(2, len(versions)) 2408 require.Equal(versions[0].ID, job.ID) 2409 require.Equal(versions[1].ID, job.ID) 2410 } 2411 2412 func TestJobEndpoint_GetJobVersions_Diff(t *testing.T) { 2413 t.Parallel() 2414 s1 := TestServer(t, nil) 2415 defer s1.Shutdown() 2416 codec := rpcClient(t, s1) 2417 testutil.WaitForLeader(t, s1.RPC) 2418 2419 // Create the register request 2420 job := mock.Job() 2421 job.Priority = 88 2422 reg := &structs.JobRegisterRequest{ 2423 Job: job, 2424 WriteRequest: structs.WriteRequest{ 2425 Region: "global", 2426 Namespace: job.Namespace, 2427 }, 2428 } 2429 2430 // Fetch the response 2431 var resp structs.JobRegisterResponse 2432 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2433 t.Fatalf("err: %v", err) 2434 } 2435 2436 // Register the job again to create another version 2437 job.Priority = 90 2438 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2439 t.Fatalf("err: %v", err) 2440 } 2441 2442 // Register the job again to create another version 2443 job.Priority = 100 2444 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2445 t.Fatalf("err: %v", err) 2446 } 2447 2448 // Lookup the job 2449 get := &structs.JobVersionsRequest{ 2450 JobID: job.ID, 2451 Diffs: true, 2452 QueryOptions: structs.QueryOptions{ 2453 Region: "global", 2454 Namespace: job.Namespace, 2455 }, 2456 } 2457 var versionsResp structs.JobVersionsResponse 2458 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil { 2459 t.Fatalf("err: %v", err) 2460 } 2461 if versionsResp.Index != resp.JobModifyIndex { 2462 t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index) 2463 } 2464 2465 // Make sure there are two job versions 2466 versions := versionsResp.Versions 2467 if l := len(versions); l != 3 { 2468 t.Fatalf("Got %d versions; want 3", l) 2469 } 2470 2471 if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 2 { 2472 t.Fatalf("bad: %+v", v) 2473 } 2474 if v := versions[1]; v.Priority != 90 || v.ID != job.ID || v.Version != 1 { 2475 t.Fatalf("bad: %+v", v) 2476 } 2477 if v := versions[2]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 { 2478 t.Fatalf("bad: %+v", v) 2479 } 2480 2481 // Ensure we got diffs 2482 diffs := versionsResp.Diffs 2483 if l := len(diffs); l != 2 { 2484 t.Fatalf("Got %d diffs; want 2", l) 2485 } 2486 d1 := diffs[0] 2487 if len(d1.Fields) != 1 { 2488 t.Fatalf("Got too many diffs: %#v", d1) 2489 } 2490 if d1.Fields[0].Name != "Priority" { 2491 t.Fatalf("Got wrong field: %#v", d1) 2492 } 2493 if d1.Fields[0].Old != "90" && d1.Fields[0].New != "100" { 2494 t.Fatalf("Got wrong field values: %#v", d1) 2495 } 2496 d2 := diffs[1] 2497 if len(d2.Fields) != 1 { 2498 t.Fatalf("Got too many diffs: %#v", d2) 2499 } 2500 if d2.Fields[0].Name != "Priority" { 2501 t.Fatalf("Got wrong field: %#v", d2) 2502 } 2503 if d2.Fields[0].Old != "88" && d1.Fields[0].New != "90" { 2504 t.Fatalf("Got wrong field values: %#v", d2) 2505 } 2506 } 2507 2508 func TestJobEndpoint_GetJobVersions_Blocking(t *testing.T) { 2509 t.Parallel() 2510 s1 := TestServer(t, nil) 2511 defer s1.Shutdown() 2512 state := s1.fsm.State() 2513 codec := rpcClient(t, s1) 2514 testutil.WaitForLeader(t, s1.RPC) 2515 2516 // Create the jobs 2517 job1 := mock.Job() 2518 job2 := mock.Job() 2519 job3 := mock.Job() 2520 job3.ID = job2.ID 2521 job3.Priority = 1 2522 2523 // Upsert a job we are not interested in first. 2524 time.AfterFunc(100*time.Millisecond, func() { 2525 if err := state.UpsertJob(100, job1); err != nil { 2526 t.Fatalf("err: %v", err) 2527 } 2528 }) 2529 2530 // Upsert another job later which should trigger the watch. 2531 time.AfterFunc(200*time.Millisecond, func() { 2532 if err := state.UpsertJob(200, job2); err != nil { 2533 t.Fatalf("err: %v", err) 2534 } 2535 }) 2536 2537 req := &structs.JobVersionsRequest{ 2538 JobID: job2.ID, 2539 QueryOptions: structs.QueryOptions{ 2540 Region: "global", 2541 Namespace: job2.Namespace, 2542 MinQueryIndex: 150, 2543 }, 2544 } 2545 start := time.Now() 2546 var resp structs.JobVersionsResponse 2547 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req, &resp); err != nil { 2548 t.Fatalf("err: %v", err) 2549 } 2550 2551 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2552 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2553 } 2554 if resp.Index != 200 { 2555 t.Fatalf("Bad index: %d %d", resp.Index, 200) 2556 } 2557 if len(resp.Versions) != 1 || resp.Versions[0].ID != job2.ID { 2558 t.Fatalf("bad: %#v", resp.Versions) 2559 } 2560 2561 // Upsert the job again which should trigger the watch. 2562 time.AfterFunc(100*time.Millisecond, func() { 2563 if err := state.UpsertJob(300, job3); err != nil { 2564 t.Fatalf("err: %v", err) 2565 } 2566 }) 2567 2568 req2 := &structs.JobVersionsRequest{ 2569 JobID: job3.ID, 2570 QueryOptions: structs.QueryOptions{ 2571 Region: "global", 2572 Namespace: job3.Namespace, 2573 MinQueryIndex: 250, 2574 }, 2575 } 2576 var resp2 structs.JobVersionsResponse 2577 start = time.Now() 2578 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req2, &resp2); err != nil { 2579 t.Fatalf("err: %v", err) 2580 } 2581 2582 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 2583 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2584 } 2585 if resp2.Index != 300 { 2586 t.Fatalf("Bad index: %d %d", resp.Index, 300) 2587 } 2588 if len(resp2.Versions) != 2 { 2589 t.Fatalf("bad: %#v", resp2.Versions) 2590 } 2591 } 2592 2593 func TestJobEndpoint_GetJobSummary(t *testing.T) { 2594 t.Parallel() 2595 s1 := TestServer(t, func(c *Config) { 2596 c.NumSchedulers = 0 // Prevent automatic dequeue 2597 }) 2598 2599 defer s1.Shutdown() 2600 codec := rpcClient(t, s1) 2601 testutil.WaitForLeader(t, s1.RPC) 2602 2603 // Create the register request 2604 job := mock.Job() 2605 reg := &structs.JobRegisterRequest{ 2606 Job: job, 2607 WriteRequest: structs.WriteRequest{ 2608 Region: "global", 2609 Namespace: job.Namespace, 2610 }, 2611 } 2612 2613 // Fetch the response 2614 var resp structs.JobRegisterResponse 2615 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 2616 t.Fatalf("err: %v", err) 2617 } 2618 job.CreateIndex = resp.JobModifyIndex 2619 job.ModifyIndex = resp.JobModifyIndex 2620 job.JobModifyIndex = resp.JobModifyIndex 2621 2622 // Lookup the job summary 2623 get := &structs.JobSummaryRequest{ 2624 JobID: job.ID, 2625 QueryOptions: structs.QueryOptions{ 2626 Region: "global", 2627 Namespace: job.Namespace, 2628 }, 2629 } 2630 var resp2 structs.JobSummaryResponse 2631 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil { 2632 t.Fatalf("err: %v", err) 2633 } 2634 if resp2.Index != resp.JobModifyIndex { 2635 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 2636 } 2637 2638 expectedJobSummary := structs.JobSummary{ 2639 JobID: job.ID, 2640 Namespace: job.Namespace, 2641 Summary: map[string]structs.TaskGroupSummary{ 2642 "web": {}, 2643 }, 2644 Children: new(structs.JobChildrenSummary), 2645 CreateIndex: job.CreateIndex, 2646 ModifyIndex: job.CreateIndex, 2647 } 2648 2649 if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) { 2650 t.Fatalf("expected: %v, actual: %v", expectedJobSummary, resp2.JobSummary) 2651 } 2652 } 2653 2654 func TestJobEndpoint_Summary_ACL(t *testing.T) { 2655 require := require.New(t) 2656 t.Parallel() 2657 2658 srv, root := TestACLServer(t, func(c *Config) { 2659 c.NumSchedulers = 0 // Prevent automatic dequeue 2660 }) 2661 defer srv.Shutdown() 2662 codec := rpcClient(t, srv) 2663 testutil.WaitForLeader(t, srv.RPC) 2664 2665 // Create the job 2666 job := mock.Job() 2667 reg := &structs.JobRegisterRequest{ 2668 Job: job, 2669 WriteRequest: structs.WriteRequest{ 2670 Region: "global", 2671 Namespace: job.Namespace, 2672 }, 2673 } 2674 reg.AuthToken = root.SecretID 2675 2676 var err error 2677 2678 // Register the job with a valid token 2679 var regResp structs.JobRegisterResponse 2680 err = msgpackrpc.CallWithCodec(codec, "Job.Register", reg, ®Resp) 2681 require.Nil(err) 2682 2683 job.CreateIndex = regResp.JobModifyIndex 2684 job.ModifyIndex = regResp.JobModifyIndex 2685 job.JobModifyIndex = regResp.JobModifyIndex 2686 2687 req := &structs.JobSummaryRequest{ 2688 JobID: job.ID, 2689 QueryOptions: structs.QueryOptions{ 2690 Region: "global", 2691 Namespace: job.Namespace, 2692 }, 2693 } 2694 2695 // Expect failure for request without a token 2696 var resp structs.JobSummaryResponse 2697 err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp) 2698 require.NotNil(err) 2699 2700 expectedJobSummary := &structs.JobSummary{ 2701 JobID: job.ID, 2702 Namespace: job.Namespace, 2703 Summary: map[string]structs.TaskGroupSummary{ 2704 "web": {}, 2705 }, 2706 Children: new(structs.JobChildrenSummary), 2707 CreateIndex: job.CreateIndex, 2708 ModifyIndex: job.ModifyIndex, 2709 } 2710 2711 // Expect success when using a management token 2712 req.AuthToken = root.SecretID 2713 var mgmtResp structs.JobSummaryResponse 2714 err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &mgmtResp) 2715 require.Nil(err) 2716 require.Equal(expectedJobSummary, mgmtResp.JobSummary) 2717 2718 // Create the namespace policy and tokens 2719 state := srv.fsm.State() 2720 2721 // Expect failure for request with an invalid token 2722 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 2723 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 2724 2725 req.AuthToken = invalidToken.SecretID 2726 var invalidResp structs.JobSummaryResponse 2727 err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &invalidResp) 2728 require.NotNil(err) 2729 2730 // Try with a valid token 2731 validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", 2732 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 2733 2734 req.AuthToken = validToken.SecretID 2735 var authResp structs.JobSummaryResponse 2736 err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &authResp) 2737 require.Nil(err) 2738 require.Equal(expectedJobSummary, authResp.JobSummary) 2739 } 2740 2741 func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) { 2742 t.Parallel() 2743 s1 := TestServer(t, nil) 2744 defer s1.Shutdown() 2745 state := s1.fsm.State() 2746 codec := rpcClient(t, s1) 2747 testutil.WaitForLeader(t, s1.RPC) 2748 2749 // Create a job and insert it 2750 job1 := mock.Job() 2751 time.AfterFunc(200*time.Millisecond, func() { 2752 if err := state.UpsertJob(100, job1); err != nil { 2753 t.Fatalf("err: %v", err) 2754 } 2755 }) 2756 2757 // Ensure the job summary request gets fired 2758 req := &structs.JobSummaryRequest{ 2759 JobID: job1.ID, 2760 QueryOptions: structs.QueryOptions{ 2761 Region: "global", 2762 Namespace: job1.Namespace, 2763 MinQueryIndex: 50, 2764 }, 2765 } 2766 var resp structs.JobSummaryResponse 2767 start := time.Now() 2768 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil { 2769 t.Fatalf("err: %v", err) 2770 } 2771 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2772 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2773 } 2774 2775 // Upsert an allocation for the job which should trigger the watch. 2776 time.AfterFunc(200*time.Millisecond, func() { 2777 alloc := mock.Alloc() 2778 alloc.JobID = job1.ID 2779 alloc.Job = job1 2780 if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil { 2781 t.Fatalf("err: %v", err) 2782 } 2783 }) 2784 req = &structs.JobSummaryRequest{ 2785 JobID: job1.ID, 2786 QueryOptions: structs.QueryOptions{ 2787 Region: "global", 2788 Namespace: job1.Namespace, 2789 MinQueryIndex: 199, 2790 }, 2791 } 2792 start = time.Now() 2793 var resp1 structs.JobSummaryResponse 2794 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil { 2795 t.Fatalf("err: %v", err) 2796 } 2797 2798 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2799 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2800 } 2801 if resp1.Index != 200 { 2802 t.Fatalf("Bad index: %d %d", resp.Index, 200) 2803 } 2804 if resp1.JobSummary == nil { 2805 t.Fatalf("bad: %#v", resp) 2806 } 2807 2808 // Job delete fires watches 2809 time.AfterFunc(100*time.Millisecond, func() { 2810 if err := state.DeleteJob(300, job1.Namespace, job1.ID); err != nil { 2811 t.Fatalf("err: %v", err) 2812 } 2813 }) 2814 2815 req.QueryOptions.MinQueryIndex = 250 2816 start = time.Now() 2817 2818 var resp2 structs.SingleJobResponse 2819 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil { 2820 t.Fatalf("err: %v", err) 2821 } 2822 2823 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 2824 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 2825 } 2826 if resp2.Index != 300 { 2827 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 2828 } 2829 if resp2.Job != nil { 2830 t.Fatalf("bad: %#v", resp2.Job) 2831 } 2832 } 2833 2834 func TestJobEndpoint_ListJobs(t *testing.T) { 2835 t.Parallel() 2836 s1 := TestServer(t, nil) 2837 defer s1.Shutdown() 2838 codec := rpcClient(t, s1) 2839 testutil.WaitForLeader(t, s1.RPC) 2840 2841 // Create the register request 2842 job := mock.Job() 2843 state := s1.fsm.State() 2844 err := state.UpsertJob(1000, job) 2845 if err != nil { 2846 t.Fatalf("err: %v", err) 2847 } 2848 2849 // Lookup the jobs 2850 get := &structs.JobListRequest{ 2851 QueryOptions: structs.QueryOptions{ 2852 Region: "global", 2853 Namespace: job.Namespace, 2854 }, 2855 } 2856 var resp2 structs.JobListResponse 2857 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil { 2858 t.Fatalf("err: %v", err) 2859 } 2860 if resp2.Index != 1000 { 2861 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 2862 } 2863 2864 if len(resp2.Jobs) != 1 { 2865 t.Fatalf("bad: %#v", resp2.Jobs) 2866 } 2867 if resp2.Jobs[0].ID != job.ID { 2868 t.Fatalf("bad: %#v", resp2.Jobs[0]) 2869 } 2870 2871 // Lookup the jobs by prefix 2872 get = &structs.JobListRequest{ 2873 QueryOptions: structs.QueryOptions{ 2874 Region: "global", 2875 Namespace: job.Namespace, 2876 Prefix: resp2.Jobs[0].ID[:4], 2877 }, 2878 } 2879 var resp3 structs.JobListResponse 2880 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil { 2881 t.Fatalf("err: %v", err) 2882 } 2883 if resp3.Index != 1000 { 2884 t.Fatalf("Bad index: %d %d", resp3.Index, 1000) 2885 } 2886 2887 if len(resp3.Jobs) != 1 { 2888 t.Fatalf("bad: %#v", resp3.Jobs) 2889 } 2890 if resp3.Jobs[0].ID != job.ID { 2891 t.Fatalf("bad: %#v", resp3.Jobs[0]) 2892 } 2893 } 2894 2895 func TestJobEndpoint_ListJobs_WithACL(t *testing.T) { 2896 require := require.New(t) 2897 t.Parallel() 2898 2899 srv, root := TestACLServer(t, func(c *Config) { 2900 c.NumSchedulers = 0 // Prevent automatic dequeue 2901 }) 2902 defer srv.Shutdown() 2903 codec := rpcClient(t, srv) 2904 testutil.WaitForLeader(t, srv.RPC) 2905 state := srv.fsm.State() 2906 2907 var err error 2908 2909 // Create the register request 2910 job := mock.Job() 2911 err = state.UpsertJob(1000, job) 2912 require.Nil(err) 2913 2914 req := &structs.JobListRequest{ 2915 QueryOptions: structs.QueryOptions{ 2916 Region: "global", 2917 Namespace: job.Namespace, 2918 }, 2919 } 2920 2921 // Expect failure for request without a token 2922 var resp structs.JobListResponse 2923 err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp) 2924 require.NotNil(err) 2925 2926 // Expect success for request with a management token 2927 var mgmtResp structs.JobListResponse 2928 req.AuthToken = root.SecretID 2929 err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &mgmtResp) 2930 require.Nil(err) 2931 require.Equal(1, len(mgmtResp.Jobs)) 2932 require.Equal(job.ID, mgmtResp.Jobs[0].ID) 2933 2934 // Expect failure for request with a token that has incorrect permissions 2935 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 2936 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 2937 2938 req.AuthToken = invalidToken.SecretID 2939 var invalidResp structs.JobListResponse 2940 err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &invalidResp) 2941 require.NotNil(err) 2942 2943 // Try with a valid token with correct permissions 2944 validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", 2945 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 2946 var validResp structs.JobListResponse 2947 req.AuthToken = validToken.SecretID 2948 2949 err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &validResp) 2950 require.Nil(err) 2951 require.Equal(1, len(validResp.Jobs)) 2952 require.Equal(job.ID, validResp.Jobs[0].ID) 2953 } 2954 2955 func TestJobEndpoint_ListJobs_Blocking(t *testing.T) { 2956 t.Parallel() 2957 s1 := TestServer(t, nil) 2958 defer s1.Shutdown() 2959 state := s1.fsm.State() 2960 codec := rpcClient(t, s1) 2961 testutil.WaitForLeader(t, s1.RPC) 2962 2963 // Create the job 2964 job := mock.Job() 2965 2966 // Upsert job triggers watches 2967 time.AfterFunc(100*time.Millisecond, func() { 2968 if err := state.UpsertJob(100, job); err != nil { 2969 t.Fatalf("err: %v", err) 2970 } 2971 }) 2972 2973 req := &structs.JobListRequest{ 2974 QueryOptions: structs.QueryOptions{ 2975 Region: "global", 2976 Namespace: job.Namespace, 2977 MinQueryIndex: 50, 2978 }, 2979 } 2980 start := time.Now() 2981 var resp structs.JobListResponse 2982 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil { 2983 t.Fatalf("err: %v", err) 2984 } 2985 2986 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 2987 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2988 } 2989 if resp.Index != 100 { 2990 t.Fatalf("Bad index: %d %d", resp.Index, 100) 2991 } 2992 if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID { 2993 t.Fatalf("bad: %#v", resp) 2994 } 2995 2996 // Job deletion triggers watches 2997 time.AfterFunc(100*time.Millisecond, func() { 2998 if err := state.DeleteJob(200, job.Namespace, job.ID); err != nil { 2999 t.Fatalf("err: %v", err) 3000 } 3001 }) 3002 3003 req.MinQueryIndex = 150 3004 start = time.Now() 3005 var resp2 structs.JobListResponse 3006 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil { 3007 t.Fatalf("err: %v", err) 3008 } 3009 3010 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 3011 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 3012 } 3013 if resp2.Index != 200 { 3014 t.Fatalf("Bad index: %d %d", resp2.Index, 200) 3015 } 3016 if len(resp2.Jobs) != 0 { 3017 t.Fatalf("bad: %#v", resp2) 3018 } 3019 } 3020 3021 func TestJobEndpoint_Allocations(t *testing.T) { 3022 t.Parallel() 3023 s1 := TestServer(t, nil) 3024 defer s1.Shutdown() 3025 codec := rpcClient(t, s1) 3026 testutil.WaitForLeader(t, s1.RPC) 3027 3028 // Create the register request 3029 alloc1 := mock.Alloc() 3030 alloc2 := mock.Alloc() 3031 alloc2.JobID = alloc1.JobID 3032 state := s1.fsm.State() 3033 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 3034 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 3035 err := state.UpsertAllocs(1000, 3036 []*structs.Allocation{alloc1, alloc2}) 3037 if err != nil { 3038 t.Fatalf("err: %v", err) 3039 } 3040 3041 // Lookup the jobs 3042 get := &structs.JobSpecificRequest{ 3043 JobID: alloc1.JobID, 3044 QueryOptions: structs.QueryOptions{ 3045 Region: "global", 3046 Namespace: alloc1.Job.Namespace, 3047 }, 3048 } 3049 var resp2 structs.JobAllocationsResponse 3050 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil { 3051 t.Fatalf("err: %v", err) 3052 } 3053 if resp2.Index != 1000 { 3054 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 3055 } 3056 3057 if len(resp2.Allocations) != 2 { 3058 t.Fatalf("bad: %#v", resp2.Allocations) 3059 } 3060 } 3061 3062 func TestJobEndpoint_Allocations_ACL(t *testing.T) { 3063 t.Parallel() 3064 require := require.New(t) 3065 3066 s1, root := TestACLServer(t, nil) 3067 defer s1.Shutdown() 3068 codec := rpcClient(t, s1) 3069 testutil.WaitForLeader(t, s1.RPC) 3070 state := s1.fsm.State() 3071 3072 // Create allocations for a job 3073 alloc1 := mock.Alloc() 3074 alloc2 := mock.Alloc() 3075 alloc2.JobID = alloc1.JobID 3076 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 3077 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 3078 err := state.UpsertAllocs(1000, 3079 []*structs.Allocation{alloc1, alloc2}) 3080 require.Nil(err) 3081 3082 // Look up allocations for that job 3083 get := &structs.JobSpecificRequest{ 3084 JobID: alloc1.JobID, 3085 QueryOptions: structs.QueryOptions{ 3086 Region: "global", 3087 Namespace: alloc1.Job.Namespace, 3088 }, 3089 } 3090 3091 // Attempt to fetch the response without a token should fail 3092 var resp structs.JobAllocationsResponse 3093 err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp) 3094 require.NotNil(err) 3095 require.Contains(err.Error(), "Permission denied") 3096 3097 // Attempt to fetch the response with an invalid token should fail 3098 invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", 3099 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 3100 3101 get.AuthToken = invalidToken.SecretID 3102 var invalidResp structs.JobAllocationsResponse 3103 err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &invalidResp) 3104 require.NotNil(err) 3105 require.Contains(err.Error(), "Permission denied") 3106 3107 // Attempt to fetch the response with valid management token should succeed 3108 get.AuthToken = root.SecretID 3109 var validResp structs.JobAllocationsResponse 3110 err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &validResp) 3111 require.Nil(err) 3112 3113 // Attempt to fetch the response with valid management token should succeed 3114 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 3115 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 3116 3117 get.AuthToken = validToken.SecretID 3118 var validResp2 structs.JobAllocationsResponse 3119 err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &validResp2) 3120 require.Nil(err) 3121 3122 require.Equal(2, len(validResp2.Allocations)) 3123 } 3124 3125 func TestJobEndpoint_Allocations_Blocking(t *testing.T) { 3126 t.Parallel() 3127 s1 := TestServer(t, nil) 3128 defer s1.Shutdown() 3129 codec := rpcClient(t, s1) 3130 testutil.WaitForLeader(t, s1.RPC) 3131 3132 // Create the register request 3133 alloc1 := mock.Alloc() 3134 alloc2 := mock.Alloc() 3135 alloc2.JobID = "job1" 3136 state := s1.fsm.State() 3137 3138 // First upsert an unrelated alloc 3139 time.AfterFunc(100*time.Millisecond, func() { 3140 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 3141 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 3142 if err != nil { 3143 t.Fatalf("err: %v", err) 3144 } 3145 }) 3146 3147 // Upsert an alloc for the job we are interested in later 3148 time.AfterFunc(200*time.Millisecond, func() { 3149 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 3150 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 3151 if err != nil { 3152 t.Fatalf("err: %v", err) 3153 } 3154 }) 3155 3156 // Lookup the jobs 3157 get := &structs.JobSpecificRequest{ 3158 JobID: "job1", 3159 QueryOptions: structs.QueryOptions{ 3160 Region: "global", 3161 Namespace: alloc1.Job.Namespace, 3162 MinQueryIndex: 150, 3163 }, 3164 } 3165 var resp structs.JobAllocationsResponse 3166 start := time.Now() 3167 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil { 3168 t.Fatalf("err: %v", err) 3169 } 3170 3171 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 3172 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 3173 } 3174 if resp.Index != 200 { 3175 t.Fatalf("Bad index: %d %d", resp.Index, 200) 3176 } 3177 if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" { 3178 t.Fatalf("bad: %#v", resp.Allocations) 3179 } 3180 } 3181 3182 func TestJobEndpoint_Evaluations(t *testing.T) { 3183 t.Parallel() 3184 s1 := TestServer(t, nil) 3185 defer s1.Shutdown() 3186 codec := rpcClient(t, s1) 3187 testutil.WaitForLeader(t, s1.RPC) 3188 3189 // Create the register request 3190 eval1 := mock.Eval() 3191 eval2 := mock.Eval() 3192 eval2.JobID = eval1.JobID 3193 state := s1.fsm.State() 3194 err := state.UpsertEvals(1000, 3195 []*structs.Evaluation{eval1, eval2}) 3196 if err != nil { 3197 t.Fatalf("err: %v", err) 3198 } 3199 3200 // Lookup the jobs 3201 get := &structs.JobSpecificRequest{ 3202 JobID: eval1.JobID, 3203 QueryOptions: structs.QueryOptions{ 3204 Region: "global", 3205 Namespace: eval1.Namespace, 3206 }, 3207 } 3208 var resp2 structs.JobEvaluationsResponse 3209 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil { 3210 t.Fatalf("err: %v", err) 3211 } 3212 if resp2.Index != 1000 { 3213 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 3214 } 3215 3216 if len(resp2.Evaluations) != 2 { 3217 t.Fatalf("bad: %#v", resp2.Evaluations) 3218 } 3219 } 3220 3221 func TestJobEndpoint_Evaluations_ACL(t *testing.T) { 3222 t.Parallel() 3223 require := require.New(t) 3224 3225 s1, root := TestACLServer(t, nil) 3226 defer s1.Shutdown() 3227 codec := rpcClient(t, s1) 3228 testutil.WaitForLeader(t, s1.RPC) 3229 state := s1.fsm.State() 3230 3231 // Create evaluations for the same job 3232 eval1 := mock.Eval() 3233 eval2 := mock.Eval() 3234 eval2.JobID = eval1.JobID 3235 err := state.UpsertEvals(1000, 3236 []*structs.Evaluation{eval1, eval2}) 3237 require.Nil(err) 3238 3239 // Lookup the jobs 3240 get := &structs.JobSpecificRequest{ 3241 JobID: eval1.JobID, 3242 QueryOptions: structs.QueryOptions{ 3243 Region: "global", 3244 Namespace: eval1.Namespace, 3245 }, 3246 } 3247 3248 // Attempt to fetch without providing a token 3249 var resp structs.JobEvaluationsResponse 3250 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp) 3251 require.NotNil(err) 3252 require.Contains(err.Error(), "Permission denied") 3253 3254 // Attempt to fetch the response with an invalid token 3255 invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", 3256 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 3257 3258 get.AuthToken = invalidToken.SecretID 3259 var invalidResp structs.JobEvaluationsResponse 3260 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &invalidResp) 3261 require.NotNil(err) 3262 require.Contains(err.Error(), "Permission denied") 3263 3264 // Attempt to fetch with valid management token should succeed 3265 get.AuthToken = root.SecretID 3266 var validResp structs.JobEvaluationsResponse 3267 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &validResp) 3268 require.Nil(err) 3269 require.Equal(2, len(validResp.Evaluations)) 3270 3271 // Attempt to fetch with valid token should succeed 3272 validToken := mock.CreatePolicyAndToken(t, state, 1003, "test-valid", 3273 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 3274 3275 get.AuthToken = validToken.SecretID 3276 var validResp2 structs.JobEvaluationsResponse 3277 err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &validResp2) 3278 require.Nil(err) 3279 require.Equal(2, len(validResp2.Evaluations)) 3280 } 3281 3282 func TestJobEndpoint_Evaluations_Blocking(t *testing.T) { 3283 t.Parallel() 3284 s1 := TestServer(t, nil) 3285 defer s1.Shutdown() 3286 codec := rpcClient(t, s1) 3287 testutil.WaitForLeader(t, s1.RPC) 3288 3289 // Create the register request 3290 eval1 := mock.Eval() 3291 eval2 := mock.Eval() 3292 eval2.JobID = "job1" 3293 state := s1.fsm.State() 3294 3295 // First upsert an unrelated eval 3296 time.AfterFunc(100*time.Millisecond, func() { 3297 err := state.UpsertEvals(100, []*structs.Evaluation{eval1}) 3298 if err != nil { 3299 t.Fatalf("err: %v", err) 3300 } 3301 }) 3302 3303 // Upsert an eval for the job we are interested in later 3304 time.AfterFunc(200*time.Millisecond, func() { 3305 err := state.UpsertEvals(200, []*structs.Evaluation{eval2}) 3306 if err != nil { 3307 t.Fatalf("err: %v", err) 3308 } 3309 }) 3310 3311 // Lookup the jobs 3312 get := &structs.JobSpecificRequest{ 3313 JobID: "job1", 3314 QueryOptions: structs.QueryOptions{ 3315 Region: "global", 3316 Namespace: eval1.Namespace, 3317 MinQueryIndex: 150, 3318 }, 3319 } 3320 var resp structs.JobEvaluationsResponse 3321 start := time.Now() 3322 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil { 3323 t.Fatalf("err: %v", err) 3324 } 3325 3326 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 3327 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 3328 } 3329 if resp.Index != 200 { 3330 t.Fatalf("Bad index: %d %d", resp.Index, 200) 3331 } 3332 if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" { 3333 t.Fatalf("bad: %#v", resp.Evaluations) 3334 } 3335 } 3336 3337 func TestJobEndpoint_Deployments(t *testing.T) { 3338 t.Parallel() 3339 s1 := TestServer(t, nil) 3340 defer s1.Shutdown() 3341 codec := rpcClient(t, s1) 3342 testutil.WaitForLeader(t, s1.RPC) 3343 state := s1.fsm.State() 3344 require := require.New(t) 3345 3346 // Create the register request 3347 j := mock.Job() 3348 d1 := mock.Deployment() 3349 d2 := mock.Deployment() 3350 d1.JobID = j.ID 3351 d2.JobID = j.ID 3352 require.Nil(state.UpsertJob(1000, j), "UpsertJob") 3353 require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment") 3354 require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment") 3355 3356 // Lookup the jobs 3357 get := &structs.JobSpecificRequest{ 3358 JobID: j.ID, 3359 QueryOptions: structs.QueryOptions{ 3360 Region: "global", 3361 Namespace: j.Namespace, 3362 }, 3363 } 3364 var resp structs.DeploymentListResponse 3365 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC") 3366 require.EqualValues(1002, resp.Index, "response index") 3367 require.Len(resp.Deployments, 2, "deployments for job") 3368 } 3369 3370 func TestJobEndpoint_Deployments_ACL(t *testing.T) { 3371 t.Parallel() 3372 require := require.New(t) 3373 3374 s1, root := TestACLServer(t, nil) 3375 defer s1.Shutdown() 3376 codec := rpcClient(t, s1) 3377 testutil.WaitForLeader(t, s1.RPC) 3378 state := s1.fsm.State() 3379 3380 // Create a job and corresponding deployments 3381 j := mock.Job() 3382 d1 := mock.Deployment() 3383 d2 := mock.Deployment() 3384 d1.JobID = j.ID 3385 d2.JobID = j.ID 3386 require.Nil(state.UpsertJob(1000, j), "UpsertJob") 3387 require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment") 3388 require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment") 3389 3390 // Lookup the jobs 3391 get := &structs.JobSpecificRequest{ 3392 JobID: j.ID, 3393 QueryOptions: structs.QueryOptions{ 3394 Region: "global", 3395 Namespace: j.Namespace, 3396 }, 3397 } 3398 // Lookup with no token should fail 3399 var resp structs.DeploymentListResponse 3400 err := msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp) 3401 require.NotNil(err) 3402 require.Contains(err.Error(), "Permission denied") 3403 3404 // Attempt to fetch the response with an invalid token 3405 invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", 3406 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 3407 3408 get.AuthToken = invalidToken.SecretID 3409 var invalidResp structs.DeploymentListResponse 3410 err = msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &invalidResp) 3411 require.NotNil(err) 3412 require.Contains(err.Error(), "Permission denied") 3413 3414 // Lookup with valid management token should succeed 3415 get.AuthToken = root.SecretID 3416 var validResp structs.DeploymentListResponse 3417 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &validResp), "RPC") 3418 require.EqualValues(1002, validResp.Index, "response index") 3419 require.Len(validResp.Deployments, 2, "deployments for job") 3420 3421 // Lookup with valid token should succeed 3422 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid", 3423 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 3424 3425 get.AuthToken = validToken.SecretID 3426 var validResp2 structs.DeploymentListResponse 3427 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &validResp2), "RPC") 3428 require.EqualValues(1002, validResp2.Index, "response index") 3429 require.Len(validResp2.Deployments, 2, "deployments for job") 3430 } 3431 3432 func TestJobEndpoint_Deployments_Blocking(t *testing.T) { 3433 t.Parallel() 3434 s1 := TestServer(t, nil) 3435 defer s1.Shutdown() 3436 codec := rpcClient(t, s1) 3437 testutil.WaitForLeader(t, s1.RPC) 3438 state := s1.fsm.State() 3439 require := require.New(t) 3440 3441 // Create the register request 3442 j := mock.Job() 3443 d1 := mock.Deployment() 3444 d2 := mock.Deployment() 3445 d2.JobID = j.ID 3446 require.Nil(state.UpsertJob(50, j), "UpsertJob") 3447 3448 // First upsert an unrelated eval 3449 time.AfterFunc(100*time.Millisecond, func() { 3450 require.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment") 3451 }) 3452 3453 // Upsert an eval for the job we are interested in later 3454 time.AfterFunc(200*time.Millisecond, func() { 3455 require.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment") 3456 }) 3457 3458 // Lookup the jobs 3459 get := &structs.JobSpecificRequest{ 3460 JobID: d2.JobID, 3461 QueryOptions: structs.QueryOptions{ 3462 Region: "global", 3463 Namespace: d2.Namespace, 3464 MinQueryIndex: 150, 3465 }, 3466 } 3467 var resp structs.DeploymentListResponse 3468 start := time.Now() 3469 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC") 3470 require.EqualValues(200, resp.Index, "response index") 3471 require.Len(resp.Deployments, 1, "deployments for job") 3472 require.Equal(d2.ID, resp.Deployments[0].ID, "returned deployment") 3473 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 3474 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 3475 } 3476 } 3477 3478 func TestJobEndpoint_LatestDeployment(t *testing.T) { 3479 t.Parallel() 3480 s1 := TestServer(t, nil) 3481 defer s1.Shutdown() 3482 codec := rpcClient(t, s1) 3483 testutil.WaitForLeader(t, s1.RPC) 3484 state := s1.fsm.State() 3485 require := require.New(t) 3486 3487 // Create the register request 3488 j := mock.Job() 3489 d1 := mock.Deployment() 3490 d2 := mock.Deployment() 3491 d1.JobID = j.ID 3492 d2.JobID = j.ID 3493 d2.CreateIndex = d1.CreateIndex + 100 3494 d2.ModifyIndex = d2.CreateIndex + 100 3495 require.Nil(state.UpsertJob(1000, j), "UpsertJob") 3496 require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment") 3497 require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment") 3498 3499 // Lookup the jobs 3500 get := &structs.JobSpecificRequest{ 3501 JobID: j.ID, 3502 QueryOptions: structs.QueryOptions{ 3503 Region: "global", 3504 Namespace: j.Namespace, 3505 }, 3506 } 3507 var resp structs.SingleDeploymentResponse 3508 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC") 3509 require.EqualValues(1002, resp.Index, "response index") 3510 require.NotNil(resp.Deployment, "want a deployment") 3511 require.Equal(d2.ID, resp.Deployment.ID, "latest deployment for job") 3512 } 3513 3514 func TestJobEndpoint_LatestDeployment_ACL(t *testing.T) { 3515 t.Parallel() 3516 require := require.New(t) 3517 3518 s1, root := TestACLServer(t, nil) 3519 defer s1.Shutdown() 3520 codec := rpcClient(t, s1) 3521 testutil.WaitForLeader(t, s1.RPC) 3522 state := s1.fsm.State() 3523 3524 // Create a job and deployments 3525 j := mock.Job() 3526 d1 := mock.Deployment() 3527 d2 := mock.Deployment() 3528 d1.JobID = j.ID 3529 d2.JobID = j.ID 3530 d2.CreateIndex = d1.CreateIndex + 100 3531 d2.ModifyIndex = d2.CreateIndex + 100 3532 require.Nil(state.UpsertJob(1000, j), "UpsertJob") 3533 require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment") 3534 require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment") 3535 3536 // Lookup the jobs 3537 get := &structs.JobSpecificRequest{ 3538 JobID: j.ID, 3539 QueryOptions: structs.QueryOptions{ 3540 Region: "global", 3541 Namespace: j.Namespace, 3542 }, 3543 } 3544 3545 // Attempt to fetch the response without a token should fail 3546 var resp structs.SingleDeploymentResponse 3547 err := msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp) 3548 require.NotNil(err) 3549 require.Contains(err.Error(), "Permission denied") 3550 3551 // Attempt to fetch the response with an invalid token should fail 3552 invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", 3553 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 3554 3555 get.AuthToken = invalidToken.SecretID 3556 var invalidResp structs.SingleDeploymentResponse 3557 err = msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &invalidResp) 3558 require.NotNil(err) 3559 require.Contains(err.Error(), "Permission denied") 3560 3561 // Fetching latest deployment with a valid management token should succeed 3562 get.AuthToken = root.SecretID 3563 var validResp structs.SingleDeploymentResponse 3564 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &validResp), "RPC") 3565 require.EqualValues(1002, validResp.Index, "response index") 3566 require.NotNil(validResp.Deployment, "want a deployment") 3567 require.Equal(d2.ID, validResp.Deployment.ID, "latest deployment for job") 3568 3569 // Fetching latest deployment with a valid token should succeed 3570 validToken := mock.CreatePolicyAndToken(t, state, 1004, "test-valid", 3571 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 3572 3573 get.AuthToken = validToken.SecretID 3574 var validResp2 structs.SingleDeploymentResponse 3575 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &validResp2), "RPC") 3576 require.EqualValues(1002, validResp2.Index, "response index") 3577 require.NotNil(validResp2.Deployment, "want a deployment") 3578 require.Equal(d2.ID, validResp2.Deployment.ID, "latest deployment for job") 3579 } 3580 3581 func TestJobEndpoint_LatestDeployment_Blocking(t *testing.T) { 3582 t.Parallel() 3583 s1 := TestServer(t, nil) 3584 defer s1.Shutdown() 3585 codec := rpcClient(t, s1) 3586 testutil.WaitForLeader(t, s1.RPC) 3587 state := s1.fsm.State() 3588 require := require.New(t) 3589 3590 // Create the register request 3591 j := mock.Job() 3592 d1 := mock.Deployment() 3593 d2 := mock.Deployment() 3594 d2.JobID = j.ID 3595 require.Nil(state.UpsertJob(50, j), "UpsertJob") 3596 3597 // First upsert an unrelated eval 3598 time.AfterFunc(100*time.Millisecond, func() { 3599 require.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment") 3600 }) 3601 3602 // Upsert an eval for the job we are interested in later 3603 time.AfterFunc(200*time.Millisecond, func() { 3604 require.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment") 3605 }) 3606 3607 // Lookup the jobs 3608 get := &structs.JobSpecificRequest{ 3609 JobID: d2.JobID, 3610 QueryOptions: structs.QueryOptions{ 3611 Region: "global", 3612 Namespace: d2.Namespace, 3613 MinQueryIndex: 150, 3614 }, 3615 } 3616 var resp structs.SingleDeploymentResponse 3617 start := time.Now() 3618 require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC") 3619 require.EqualValues(200, resp.Index, "response index") 3620 require.NotNil(resp.Deployment, "deployment for job") 3621 require.Equal(d2.ID, resp.Deployment.ID, "returned deployment") 3622 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 3623 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 3624 } 3625 } 3626 3627 func TestJobEndpoint_Plan_ACL(t *testing.T) { 3628 t.Parallel() 3629 s1, root := TestACLServer(t, func(c *Config) { 3630 c.NumSchedulers = 0 // Prevent automatic dequeue 3631 }) 3632 defer s1.Shutdown() 3633 codec := rpcClient(t, s1) 3634 testutil.WaitForLeader(t, s1.RPC) 3635 3636 // Create a plan request 3637 job := mock.Job() 3638 planReq := &structs.JobPlanRequest{ 3639 Job: job, 3640 Diff: true, 3641 WriteRequest: structs.WriteRequest{ 3642 Region: "global", 3643 Namespace: job.Namespace, 3644 }, 3645 } 3646 3647 // Try without a token, expect failure 3648 var planResp structs.JobPlanResponse 3649 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err == nil { 3650 t.Fatalf("expected error") 3651 } 3652 3653 // Try with a token 3654 planReq.AuthToken = root.SecretID 3655 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 3656 t.Fatalf("err: %v", err) 3657 } 3658 } 3659 3660 func TestJobEndpoint_Plan_WithDiff(t *testing.T) { 3661 t.Parallel() 3662 s1 := TestServer(t, func(c *Config) { 3663 c.NumSchedulers = 0 // Prevent automatic dequeue 3664 }) 3665 defer s1.Shutdown() 3666 codec := rpcClient(t, s1) 3667 testutil.WaitForLeader(t, s1.RPC) 3668 3669 // Create the register request 3670 job := mock.Job() 3671 req := &structs.JobRegisterRequest{ 3672 Job: job, 3673 WriteRequest: structs.WriteRequest{ 3674 Region: "global", 3675 Namespace: job.Namespace, 3676 }, 3677 } 3678 3679 // Fetch the response 3680 var resp structs.JobRegisterResponse 3681 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 3682 t.Fatalf("err: %v", err) 3683 } 3684 if resp.Index == 0 { 3685 t.Fatalf("bad index: %d", resp.Index) 3686 } 3687 3688 // Create a plan request 3689 planReq := &structs.JobPlanRequest{ 3690 Job: job, 3691 Diff: true, 3692 WriteRequest: structs.WriteRequest{ 3693 Region: "global", 3694 Namespace: job.Namespace, 3695 }, 3696 } 3697 3698 // Fetch the response 3699 var planResp structs.JobPlanResponse 3700 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 3701 t.Fatalf("err: %v", err) 3702 } 3703 3704 // Check the response 3705 if planResp.JobModifyIndex == 0 { 3706 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 3707 } 3708 if planResp.Annotations == nil { 3709 t.Fatalf("no annotations") 3710 } 3711 if planResp.Diff == nil { 3712 t.Fatalf("no diff") 3713 } 3714 if len(planResp.FailedTGAllocs) == 0 { 3715 t.Fatalf("no failed task group alloc metrics") 3716 } 3717 } 3718 3719 func TestJobEndpoint_Plan_NoDiff(t *testing.T) { 3720 t.Parallel() 3721 s1 := TestServer(t, func(c *Config) { 3722 c.NumSchedulers = 0 // Prevent automatic dequeue 3723 }) 3724 defer s1.Shutdown() 3725 codec := rpcClient(t, s1) 3726 testutil.WaitForLeader(t, s1.RPC) 3727 3728 // Create the register request 3729 job := mock.Job() 3730 req := &structs.JobRegisterRequest{ 3731 Job: job, 3732 WriteRequest: structs.WriteRequest{ 3733 Region: "global", 3734 Namespace: job.Namespace, 3735 }, 3736 } 3737 3738 // Fetch the response 3739 var resp structs.JobRegisterResponse 3740 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 3741 t.Fatalf("err: %v", err) 3742 } 3743 if resp.Index == 0 { 3744 t.Fatalf("bad index: %d", resp.Index) 3745 } 3746 3747 // Create a plan request 3748 planReq := &structs.JobPlanRequest{ 3749 Job: job, 3750 Diff: false, 3751 WriteRequest: structs.WriteRequest{ 3752 Region: "global", 3753 Namespace: job.Namespace, 3754 }, 3755 } 3756 3757 // Fetch the response 3758 var planResp structs.JobPlanResponse 3759 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 3760 t.Fatalf("err: %v", err) 3761 } 3762 3763 // Check the response 3764 if planResp.JobModifyIndex == 0 { 3765 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 3766 } 3767 if planResp.Annotations == nil { 3768 t.Fatalf("no annotations") 3769 } 3770 if planResp.Diff != nil { 3771 t.Fatalf("got diff") 3772 } 3773 if len(planResp.FailedTGAllocs) == 0 { 3774 t.Fatalf("no failed task group alloc metrics") 3775 } 3776 } 3777 3778 func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) { 3779 t.Parallel() 3780 s1 := TestServer(t, func(c *Config) { 3781 c.NumSchedulers = 0 // Prevent automatic dequeue 3782 }) 3783 defer s1.Shutdown() 3784 codec := rpcClient(t, s1) 3785 testutil.WaitForLeader(t, s1.RPC) 3786 3787 // Enable vault 3788 tr, f := true, false 3789 s1.config.VaultConfig.Enabled = &tr 3790 s1.config.VaultConfig.AllowUnauthenticated = &f 3791 3792 // Replace the Vault Client on the server 3793 tvc := &TestVaultClient{} 3794 s1.vault = tvc 3795 3796 policy := "foo" 3797 goodToken := uuid.Generate() 3798 goodPolicies := []string{"foo", "bar", "baz"} 3799 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 3800 3801 // Create the register request with a job asking for a vault policy 3802 job := mock.Job() 3803 job.VaultToken = goodToken 3804 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 3805 Policies: []string{policy}, 3806 ChangeMode: structs.VaultChangeModeRestart, 3807 } 3808 req := &structs.JobRegisterRequest{ 3809 Job: job, 3810 WriteRequest: structs.WriteRequest{ 3811 Region: "global", 3812 Namespace: job.Namespace, 3813 }, 3814 } 3815 3816 // Fetch the response 3817 var resp structs.JobRegisterResponse 3818 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 3819 t.Fatalf("bad: %v", err) 3820 } 3821 3822 // Check for the job in the FSM 3823 state := s1.fsm.State() 3824 ws := memdb.NewWatchSet() 3825 out, err := state.JobByID(ws, job.Namespace, job.ID) 3826 if err != nil { 3827 t.Fatalf("err: %v", err) 3828 } 3829 if out == nil { 3830 t.Fatalf("expected job") 3831 } 3832 if out.CreateIndex != resp.JobModifyIndex { 3833 t.Fatalf("index mis-match") 3834 } 3835 3836 // Check that there is an implicit vault constraint 3837 constraints := out.TaskGroups[0].Constraints 3838 if len(constraints) != 1 { 3839 t.Fatalf("Expected an implicit constraint") 3840 } 3841 3842 if !constraints[0].Equal(vaultConstraint) { 3843 t.Fatalf("Expected implicit vault constraint") 3844 } 3845 } 3846 3847 func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) { 3848 t.Parallel() 3849 s1 := TestServer(t, func(c *Config) { 3850 c.NumSchedulers = 0 // Prevent automatic dequeue 3851 }) 3852 defer s1.Shutdown() 3853 codec := rpcClient(t, s1) 3854 testutil.WaitForLeader(t, s1.RPC) 3855 3856 // Create the register request with a job asking for a template that sends a 3857 // signal 3858 job := mock.Job() 3859 signal1 := "SIGUSR1" 3860 signal2 := "SIGHUP" 3861 job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{ 3862 { 3863 SourcePath: "foo", 3864 DestPath: "bar", 3865 ChangeMode: structs.TemplateChangeModeSignal, 3866 ChangeSignal: signal1, 3867 }, 3868 { 3869 SourcePath: "foo", 3870 DestPath: "baz", 3871 ChangeMode: structs.TemplateChangeModeSignal, 3872 ChangeSignal: signal2, 3873 }, 3874 } 3875 req := &structs.JobRegisterRequest{ 3876 Job: job, 3877 WriteRequest: structs.WriteRequest{ 3878 Region: "global", 3879 Namespace: job.Namespace, 3880 }, 3881 } 3882 3883 // Fetch the response 3884 var resp structs.JobRegisterResponse 3885 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 3886 t.Fatalf("bad: %v", err) 3887 } 3888 3889 // Check for the job in the FSM 3890 state := s1.fsm.State() 3891 ws := memdb.NewWatchSet() 3892 out, err := state.JobByID(ws, job.Namespace, job.ID) 3893 if err != nil { 3894 t.Fatalf("err: %v", err) 3895 } 3896 if out == nil { 3897 t.Fatalf("expected job") 3898 } 3899 if out.CreateIndex != resp.JobModifyIndex { 3900 t.Fatalf("index mis-match") 3901 } 3902 3903 // Check that there is an implicit signal constraint 3904 constraints := out.TaskGroups[0].Constraints 3905 if len(constraints) != 1 { 3906 t.Fatalf("Expected an implicit constraint") 3907 } 3908 3909 sigConstraint := getSignalConstraint([]string{signal1, signal2}) 3910 if !strings.HasPrefix(sigConstraint.RTarget, "SIGHUP") { 3911 t.Fatalf("signals not sorted: %v", sigConstraint.RTarget) 3912 } 3913 3914 if !constraints[0].Equal(sigConstraint) { 3915 t.Fatalf("Expected implicit vault constraint") 3916 } 3917 } 3918 3919 func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) { 3920 t.Parallel() 3921 // Create a mock job with an invalid config 3922 job := mock.Job() 3923 job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{ 3924 "foo": "bar", 3925 } 3926 3927 err, warnings := validateJob(job) 3928 if err == nil || !strings.Contains(err.Error(), "-> config") { 3929 t.Fatalf("Expected config error; got %v", err) 3930 } 3931 3932 if warnings != nil { 3933 t.Fatalf("got unexpected warnings: %v", warnings) 3934 } 3935 } 3936 3937 func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) { 3938 t.Parallel() 3939 // Create a mock job that wants to send a signal to a driver that can't 3940 job := mock.Job() 3941 job.TaskGroups[0].Tasks[0].Driver = "qemu" 3942 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 3943 Policies: []string{"foo"}, 3944 ChangeMode: structs.VaultChangeModeSignal, 3945 ChangeSignal: "SIGUSR1", 3946 } 3947 3948 err, warnings := validateJob(job) 3949 if err == nil || !strings.Contains(err.Error(), "support sending signals") { 3950 t.Fatalf("Expected signal feasibility error; got %v", err) 3951 } 3952 3953 if warnings != nil { 3954 t.Fatalf("got unexpected warnings: %v", warnings) 3955 } 3956 } 3957 3958 func TestJobEndpoint_ValidateJob_KillSignal(t *testing.T) { 3959 require := require.New(t) 3960 t.Parallel() 3961 3962 // test validate fails if the driver does not support sending signals, but a 3963 // stop_signal has been specified 3964 { 3965 job := mock.Job() 3966 job.TaskGroups[0].Tasks[0].Driver = "qemu" // qemu does not support sending signals 3967 job.TaskGroups[0].Tasks[0].KillSignal = "SIGINT" 3968 3969 err, warnings := validateJob(job) 3970 require.NotNil(err) 3971 require.True(strings.Contains(err.Error(), "support sending signals")) 3972 require.Nil(warnings) 3973 } 3974 3975 // test validate succeeds if the driver does support sending signals, and 3976 // a stop_signal has been specified 3977 { 3978 job := mock.Job() 3979 job.TaskGroups[0].Tasks[0].KillSignal = "SIGINT" 3980 3981 err, warnings := validateJob(job) 3982 require.Nil(err) 3983 require.Nil(warnings) 3984 } 3985 } 3986 3987 func TestJobEndpoint_ValidateJobUpdate(t *testing.T) { 3988 t.Parallel() 3989 require := require.New(t) 3990 old := mock.Job() 3991 new := mock.Job() 3992 3993 if err := validateJobUpdate(old, new); err != nil { 3994 t.Errorf("expected update to be valid but got: %v", err) 3995 } 3996 3997 new.Type = "batch" 3998 if err := validateJobUpdate(old, new); err == nil { 3999 t.Errorf("expected err when setting new job to a different type") 4000 } else { 4001 t.Log(err) 4002 } 4003 4004 new = mock.Job() 4005 new.Periodic = &structs.PeriodicConfig{Enabled: true} 4006 if err := validateJobUpdate(old, new); err == nil { 4007 t.Errorf("expected err when setting new job to periodic") 4008 } else { 4009 t.Log(err) 4010 } 4011 4012 new = mock.Job() 4013 new.ParameterizedJob = &structs.ParameterizedJobConfig{} 4014 if err := validateJobUpdate(old, new); err == nil { 4015 t.Errorf("expected err when setting new job to parameterized") 4016 } else { 4017 t.Log(err) 4018 } 4019 4020 new = mock.Job() 4021 new.Dispatched = true 4022 require.Error(validateJobUpdate(old, new), 4023 "expected err when setting new job to dispatched") 4024 require.Error(validateJobUpdate(nil, new), 4025 "expected err when setting new job to dispatched") 4026 require.Error(validateJobUpdate(new, old), 4027 "expected err when setting dispatched to false") 4028 require.NoError(validateJobUpdate(nil, old)) 4029 } 4030 4031 func TestJobEndpoint_ValidateJobUpdate_ACL(t *testing.T) { 4032 t.Parallel() 4033 require := require.New(t) 4034 4035 s1, root := TestACLServer(t, func(c *Config) { 4036 c.NumSchedulers = 0 // Prevent automatic dequeue 4037 }) 4038 defer s1.Shutdown() 4039 codec := rpcClient(t, s1) 4040 testutil.WaitForLeader(t, s1.RPC) 4041 4042 job := mock.Job() 4043 4044 req := &structs.JobValidateRequest{ 4045 Job: job, 4046 WriteRequest: structs.WriteRequest{ 4047 Region: "global", 4048 Namespace: job.Namespace, 4049 }, 4050 } 4051 4052 // Attempt to update without providing a valid token 4053 var resp structs.JobValidateResponse 4054 err := msgpackrpc.CallWithCodec(codec, "Job.Validate", req, &resp) 4055 require.NotNil(err) 4056 4057 // Update with a valid token 4058 req.AuthToken = root.SecretID 4059 var validResp structs.JobValidateResponse 4060 err = msgpackrpc.CallWithCodec(codec, "Job.Validate", req, &validResp) 4061 require.Nil(err) 4062 4063 require.Equal("", validResp.Error) 4064 require.Equal("", validResp.Warnings) 4065 } 4066 4067 func TestJobEndpoint_Dispatch_ACL(t *testing.T) { 4068 t.Parallel() 4069 require := require.New(t) 4070 4071 s1, root := TestACLServer(t, func(c *Config) { 4072 c.NumSchedulers = 0 // Prevent automatic dequeue 4073 }) 4074 4075 defer s1.Shutdown() 4076 codec := rpcClient(t, s1) 4077 testutil.WaitForLeader(t, s1.RPC) 4078 state := s1.fsm.State() 4079 4080 // Create a parameterized job 4081 job := mock.BatchJob() 4082 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 4083 err := state.UpsertJob(400, job) 4084 require.Nil(err) 4085 4086 req := &structs.JobDispatchRequest{ 4087 JobID: job.ID, 4088 WriteRequest: structs.WriteRequest{ 4089 Region: "global", 4090 Namespace: job.Namespace, 4091 }, 4092 } 4093 4094 // Attempt to fetch the response without a token should fail 4095 var resp structs.JobDispatchResponse 4096 err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &resp) 4097 require.NotNil(err) 4098 require.Contains(err.Error(), "Permission denied") 4099 4100 // Attempt to fetch the response with an invalid token should fail 4101 invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", 4102 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 4103 req.AuthToken = invalidToken.SecretID 4104 4105 var invalidResp structs.JobDispatchResponse 4106 err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &invalidResp) 4107 require.NotNil(err) 4108 require.Contains(err.Error(), "Permission denied") 4109 4110 // Dispatch with a valid management token should succeed 4111 req.AuthToken = root.SecretID 4112 4113 var validResp structs.JobDispatchResponse 4114 err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &validResp) 4115 require.Nil(err) 4116 require.NotNil(validResp.EvalID) 4117 require.NotNil(validResp.DispatchedJobID) 4118 require.NotEqual(validResp.DispatchedJobID, "") 4119 4120 // Dispatch with a valid token should succeed 4121 validToken := mock.CreatePolicyAndToken(t, state, 1003, "test-valid", 4122 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityDispatchJob})) 4123 req.AuthToken = validToken.SecretID 4124 4125 var validResp2 structs.JobDispatchResponse 4126 err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &validResp2) 4127 require.Nil(err) 4128 require.NotNil(validResp2.EvalID) 4129 require.NotNil(validResp2.DispatchedJobID) 4130 require.NotEqual(validResp2.DispatchedJobID, "") 4131 4132 ws := memdb.NewWatchSet() 4133 out, err := state.JobByID(ws, job.Namespace, validResp2.DispatchedJobID) 4134 require.Nil(err) 4135 require.NotNil(out) 4136 require.Equal(out.ParentID, job.ID) 4137 4138 // Look up the evaluation 4139 eval, err := state.EvalByID(ws, validResp2.EvalID) 4140 require.Nil(err) 4141 require.NotNil(eval) 4142 require.Equal(eval.CreateIndex, validResp2.EvalCreateIndex) 4143 } 4144 4145 func TestJobEndpoint_Dispatch(t *testing.T) { 4146 t.Parallel() 4147 4148 // No requirements 4149 d1 := mock.BatchJob() 4150 d1.ParameterizedJob = &structs.ParameterizedJobConfig{} 4151 4152 // Require input data 4153 d2 := mock.BatchJob() 4154 d2.ParameterizedJob = &structs.ParameterizedJobConfig{ 4155 Payload: structs.DispatchPayloadRequired, 4156 } 4157 4158 // Disallow input data 4159 d3 := mock.BatchJob() 4160 d3.ParameterizedJob = &structs.ParameterizedJobConfig{ 4161 Payload: structs.DispatchPayloadForbidden, 4162 } 4163 4164 // Require meta 4165 d4 := mock.BatchJob() 4166 d4.ParameterizedJob = &structs.ParameterizedJobConfig{ 4167 MetaRequired: []string{"foo", "bar"}, 4168 } 4169 4170 // Optional meta 4171 d5 := mock.BatchJob() 4172 d5.ParameterizedJob = &structs.ParameterizedJobConfig{ 4173 MetaOptional: []string{"foo", "bar"}, 4174 } 4175 4176 // Periodic dispatch job 4177 d6 := mock.PeriodicJob() 4178 d6.ParameterizedJob = &structs.ParameterizedJobConfig{} 4179 4180 d7 := mock.BatchJob() 4181 d7.ParameterizedJob = &structs.ParameterizedJobConfig{} 4182 d7.Stop = true 4183 4184 reqNoInputNoMeta := &structs.JobDispatchRequest{} 4185 reqInputDataNoMeta := &structs.JobDispatchRequest{ 4186 Payload: []byte("hello world"), 4187 } 4188 reqNoInputDataMeta := &structs.JobDispatchRequest{ 4189 Meta: map[string]string{ 4190 "foo": "f1", 4191 "bar": "f2", 4192 }, 4193 } 4194 reqInputDataMeta := &structs.JobDispatchRequest{ 4195 Payload: []byte("hello world"), 4196 Meta: map[string]string{ 4197 "foo": "f1", 4198 "bar": "f2", 4199 }, 4200 } 4201 reqBadMeta := &structs.JobDispatchRequest{ 4202 Payload: []byte("hello world"), 4203 Meta: map[string]string{ 4204 "foo": "f1", 4205 "bar": "f2", 4206 "baz": "f3", 4207 }, 4208 } 4209 reqInputDataTooLarge := &structs.JobDispatchRequest{ 4210 Payload: make([]byte, DispatchPayloadSizeLimit+100), 4211 } 4212 4213 type testCase struct { 4214 name string 4215 parameterizedJob *structs.Job 4216 dispatchReq *structs.JobDispatchRequest 4217 noEval bool 4218 err bool 4219 errStr string 4220 } 4221 cases := []testCase{ 4222 { 4223 name: "optional input data w/ data", 4224 parameterizedJob: d1, 4225 dispatchReq: reqInputDataNoMeta, 4226 err: false, 4227 }, 4228 { 4229 name: "optional input data w/o data", 4230 parameterizedJob: d1, 4231 dispatchReq: reqNoInputNoMeta, 4232 err: false, 4233 }, 4234 { 4235 name: "require input data w/ data", 4236 parameterizedJob: d2, 4237 dispatchReq: reqInputDataNoMeta, 4238 err: false, 4239 }, 4240 { 4241 name: "require input data w/o data", 4242 parameterizedJob: d2, 4243 dispatchReq: reqNoInputNoMeta, 4244 err: true, 4245 errStr: "not provided but required", 4246 }, 4247 { 4248 name: "disallow input data w/o data", 4249 parameterizedJob: d3, 4250 dispatchReq: reqNoInputNoMeta, 4251 err: false, 4252 }, 4253 { 4254 name: "disallow input data w/ data", 4255 parameterizedJob: d3, 4256 dispatchReq: reqInputDataNoMeta, 4257 err: true, 4258 errStr: "provided but forbidden", 4259 }, 4260 { 4261 name: "require meta w/ meta", 4262 parameterizedJob: d4, 4263 dispatchReq: reqInputDataMeta, 4264 err: false, 4265 }, 4266 { 4267 name: "require meta w/o meta", 4268 parameterizedJob: d4, 4269 dispatchReq: reqNoInputNoMeta, 4270 err: true, 4271 errStr: "did not provide required meta keys", 4272 }, 4273 { 4274 name: "optional meta w/ meta", 4275 parameterizedJob: d5, 4276 dispatchReq: reqNoInputDataMeta, 4277 err: false, 4278 }, 4279 { 4280 name: "optional meta w/o meta", 4281 parameterizedJob: d5, 4282 dispatchReq: reqNoInputNoMeta, 4283 err: false, 4284 }, 4285 { 4286 name: "optional meta w/ bad meta", 4287 parameterizedJob: d5, 4288 dispatchReq: reqBadMeta, 4289 err: true, 4290 errStr: "unpermitted metadata keys", 4291 }, 4292 { 4293 name: "optional input w/ too big of input", 4294 parameterizedJob: d1, 4295 dispatchReq: reqInputDataTooLarge, 4296 err: true, 4297 errStr: "Payload exceeds maximum size", 4298 }, 4299 { 4300 name: "periodic job dispatched, ensure no eval", 4301 parameterizedJob: d6, 4302 dispatchReq: reqNoInputNoMeta, 4303 noEval: true, 4304 }, 4305 { 4306 name: "periodic job stopped, ensure error", 4307 parameterizedJob: d7, 4308 dispatchReq: reqNoInputNoMeta, 4309 err: true, 4310 errStr: "stopped", 4311 }, 4312 } 4313 4314 for _, tc := range cases { 4315 t.Run(tc.name, func(t *testing.T) { 4316 s1 := TestServer(t, func(c *Config) { 4317 c.NumSchedulers = 0 // Prevent automatic dequeue 4318 }) 4319 defer s1.Shutdown() 4320 codec := rpcClient(t, s1) 4321 testutil.WaitForLeader(t, s1.RPC) 4322 4323 // Create the register request 4324 regReq := &structs.JobRegisterRequest{ 4325 Job: tc.parameterizedJob, 4326 WriteRequest: structs.WriteRequest{ 4327 Region: "global", 4328 Namespace: tc.parameterizedJob.Namespace, 4329 }, 4330 } 4331 4332 // Fetch the response 4333 var regResp structs.JobRegisterResponse 4334 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", regReq, ®Resp); err != nil { 4335 t.Fatalf("err: %v", err) 4336 } 4337 4338 // Now try to dispatch 4339 tc.dispatchReq.JobID = tc.parameterizedJob.ID 4340 tc.dispatchReq.WriteRequest = structs.WriteRequest{ 4341 Region: "global", 4342 Namespace: tc.parameterizedJob.Namespace, 4343 } 4344 4345 var dispatchResp structs.JobDispatchResponse 4346 dispatchErr := msgpackrpc.CallWithCodec(codec, "Job.Dispatch", tc.dispatchReq, &dispatchResp) 4347 4348 if dispatchErr == nil { 4349 if tc.err { 4350 t.Fatalf("Expected error: %v", dispatchErr) 4351 } 4352 4353 // Check that we got an eval and job id back 4354 switch dispatchResp.EvalID { 4355 case "": 4356 if !tc.noEval { 4357 t.Fatalf("Bad response") 4358 } 4359 default: 4360 if tc.noEval { 4361 t.Fatalf("Got eval %q", dispatchResp.EvalID) 4362 } 4363 } 4364 4365 if dispatchResp.DispatchedJobID == "" { 4366 t.Fatalf("Bad response") 4367 } 4368 4369 state := s1.fsm.State() 4370 ws := memdb.NewWatchSet() 4371 out, err := state.JobByID(ws, tc.parameterizedJob.Namespace, dispatchResp.DispatchedJobID) 4372 if err != nil { 4373 t.Fatalf("err: %v", err) 4374 } 4375 if out == nil { 4376 t.Fatalf("expected job") 4377 } 4378 if out.CreateIndex != dispatchResp.JobCreateIndex { 4379 t.Fatalf("index mis-match") 4380 } 4381 if out.ParentID != tc.parameterizedJob.ID { 4382 t.Fatalf("bad parent ID") 4383 } 4384 if !out.Dispatched { 4385 t.Fatal("expected dispatched job") 4386 } 4387 if out.IsParameterized() { 4388 t.Fatal("dispatched job should not be parameterized") 4389 } 4390 if out.ParameterizedJob == nil { 4391 t.Fatal("parameter job config should exist") 4392 } 4393 4394 if tc.noEval { 4395 return 4396 } 4397 4398 // Lookup the evaluation 4399 eval, err := state.EvalByID(ws, dispatchResp.EvalID) 4400 if err != nil { 4401 t.Fatalf("err: %v", err) 4402 } 4403 4404 if eval == nil { 4405 t.Fatalf("expected eval") 4406 } 4407 if eval.CreateIndex != dispatchResp.EvalCreateIndex { 4408 t.Fatalf("index mis-match") 4409 } 4410 } else { 4411 if !tc.err { 4412 t.Fatalf("Got unexpected error: %v", dispatchErr) 4413 } else if !strings.Contains(dispatchErr.Error(), tc.errStr) { 4414 t.Fatalf("Expected err to include %q; got %v", tc.errStr, dispatchErr) 4415 } 4416 } 4417 }) 4418 } 4419 }