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