github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/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 "github.com/hashicorp/net-rpc-msgpackrpc" 12 "github.com/hashicorp/nomad/helper" 13 "github.com/hashicorp/nomad/nomad/mock" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/testutil" 16 "github.com/kr/pretty" 17 "github.com/stretchr/testify/assert" 18 ) 19 20 func TestJobEndpoint_Register(t *testing.T) { 21 t.Parallel() 22 s1 := testServer(t, func(c *Config) { 23 c.NumSchedulers = 0 // Prevent automatic dequeue 24 }) 25 defer s1.Shutdown() 26 codec := rpcClient(t, s1) 27 testutil.WaitForLeader(t, s1.RPC) 28 29 // Create the register request 30 job := mock.Job() 31 req := &structs.JobRegisterRequest{ 32 Job: job, 33 WriteRequest: structs.WriteRequest{Region: "global"}, 34 } 35 36 // Fetch the response 37 var resp structs.JobRegisterResponse 38 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 39 t.Fatalf("err: %v", err) 40 } 41 if resp.Index == 0 { 42 t.Fatalf("bad index: %d", resp.Index) 43 } 44 45 // Check for the node in the FSM 46 state := s1.fsm.State() 47 ws := memdb.NewWatchSet() 48 out, err := state.JobByID(ws, job.ID) 49 if err != nil { 50 t.Fatalf("err: %v", err) 51 } 52 if out == nil { 53 t.Fatalf("expected job") 54 } 55 if out.CreateIndex != resp.JobModifyIndex { 56 t.Fatalf("index mis-match") 57 } 58 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 59 expectedServiceName := "web-frontend" 60 if serviceName != expectedServiceName { 61 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 62 } 63 64 // Lookup the evaluation 65 eval, err := state.EvalByID(ws, resp.EvalID) 66 if err != nil { 67 t.Fatalf("err: %v", err) 68 } 69 if eval == nil { 70 t.Fatalf("expected eval") 71 } 72 if eval.CreateIndex != resp.EvalCreateIndex { 73 t.Fatalf("index mis-match") 74 } 75 76 if eval.Priority != job.Priority { 77 t.Fatalf("bad: %#v", eval) 78 } 79 if eval.Type != job.Type { 80 t.Fatalf("bad: %#v", eval) 81 } 82 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 83 t.Fatalf("bad: %#v", eval) 84 } 85 if eval.JobID != job.ID { 86 t.Fatalf("bad: %#v", eval) 87 } 88 if eval.JobModifyIndex != resp.JobModifyIndex { 89 t.Fatalf("bad: %#v", eval) 90 } 91 if eval.Status != structs.EvalStatusPending { 92 t.Fatalf("bad: %#v", eval) 93 } 94 } 95 96 func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) { 97 t.Parallel() 98 s1 := testServer(t, func(c *Config) { 99 c.NumSchedulers = 0 // Prevent automatic dequeue 100 }) 101 defer s1.Shutdown() 102 codec := rpcClient(t, s1) 103 testutil.WaitForLeader(t, s1.RPC) 104 105 // Create the register request with a job containing an invalid driver 106 // config 107 job := mock.Job() 108 job.TaskGroups[0].Tasks[0].Config["foo"] = 1 109 req := &structs.JobRegisterRequest{ 110 Job: job, 111 WriteRequest: structs.WriteRequest{Region: "global"}, 112 } 113 114 // Fetch the response 115 var resp structs.JobRegisterResponse 116 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 117 if err == nil { 118 t.Fatalf("expected a validation error") 119 } 120 121 if !strings.Contains(err.Error(), "-> config:") { 122 t.Fatalf("expected a driver config validation error but got: %v", err) 123 } 124 } 125 126 func TestJobEndpoint_Register_Payload(t *testing.T) { 127 t.Parallel() 128 s1 := testServer(t, func(c *Config) { 129 c.NumSchedulers = 0 // Prevent automatic dequeue 130 }) 131 defer s1.Shutdown() 132 codec := rpcClient(t, s1) 133 testutil.WaitForLeader(t, s1.RPC) 134 135 // Create the register request with a job containing an invalid driver 136 // config 137 job := mock.Job() 138 job.Payload = []byte{0x1} 139 req := &structs.JobRegisterRequest{ 140 Job: job, 141 WriteRequest: structs.WriteRequest{Region: "global"}, 142 } 143 144 // Fetch the response 145 var resp structs.JobRegisterResponse 146 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 147 if err == nil { 148 t.Fatalf("expected a validation error") 149 } 150 151 if !strings.Contains(err.Error(), "payload") { 152 t.Fatalf("expected a payload error but got: %v", err) 153 } 154 } 155 156 func TestJobEndpoint_Register_Existing(t *testing.T) { 157 t.Parallel() 158 s1 := testServer(t, func(c *Config) { 159 c.NumSchedulers = 0 // Prevent automatic dequeue 160 }) 161 defer s1.Shutdown() 162 codec := rpcClient(t, s1) 163 testutil.WaitForLeader(t, s1.RPC) 164 165 // Create the register request 166 job := mock.Job() 167 req := &structs.JobRegisterRequest{ 168 Job: job, 169 WriteRequest: structs.WriteRequest{Region: "global"}, 170 } 171 172 // Fetch the response 173 var resp structs.JobRegisterResponse 174 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 175 t.Fatalf("err: %v", err) 176 } 177 if resp.Index == 0 { 178 t.Fatalf("bad index: %d", resp.Index) 179 } 180 181 // Update the job definition 182 job2 := mock.Job() 183 job2.Priority = 100 184 job2.ID = job.ID 185 req.Job = job2 186 187 // Attempt update 188 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 189 t.Fatalf("err: %v", err) 190 } 191 if resp.Index == 0 { 192 t.Fatalf("bad index: %d", resp.Index) 193 } 194 195 // Check for the node in the FSM 196 state := s1.fsm.State() 197 ws := memdb.NewWatchSet() 198 out, err := state.JobByID(ws, job.ID) 199 if err != nil { 200 t.Fatalf("err: %v", err) 201 } 202 if out == nil { 203 t.Fatalf("expected job") 204 } 205 if out.ModifyIndex != resp.JobModifyIndex { 206 t.Fatalf("index mis-match") 207 } 208 if out.Priority != 100 { 209 t.Fatalf("expected update") 210 } 211 if out.Version != 1 { 212 t.Fatalf("expected update") 213 } 214 215 // Lookup the evaluation 216 eval, err := state.EvalByID(ws, resp.EvalID) 217 if err != nil { 218 t.Fatalf("err: %v", err) 219 } 220 if eval == nil { 221 t.Fatalf("expected eval") 222 } 223 if eval.CreateIndex != resp.EvalCreateIndex { 224 t.Fatalf("index mis-match") 225 } 226 227 if eval.Priority != job2.Priority { 228 t.Fatalf("bad: %#v", eval) 229 } 230 if eval.Type != job2.Type { 231 t.Fatalf("bad: %#v", eval) 232 } 233 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 234 t.Fatalf("bad: %#v", eval) 235 } 236 if eval.JobID != job2.ID { 237 t.Fatalf("bad: %#v", eval) 238 } 239 if eval.JobModifyIndex != resp.JobModifyIndex { 240 t.Fatalf("bad: %#v", eval) 241 } 242 if eval.Status != structs.EvalStatusPending { 243 t.Fatalf("bad: %#v", eval) 244 } 245 246 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 247 t.Fatalf("err: %v", err) 248 } 249 if resp.Index == 0 { 250 t.Fatalf("bad index: %d", resp.Index) 251 } 252 253 // Check to ensure the job version didn't get bumped becasue we submitted 254 // the same job 255 state = s1.fsm.State() 256 ws = memdb.NewWatchSet() 257 out, err = state.JobByID(ws, job.ID) 258 if err != nil { 259 t.Fatalf("err: %v", err) 260 } 261 if out == nil { 262 t.Fatalf("expected job") 263 } 264 if out.Version != 1 { 265 t.Fatalf("expected no update; got %v; diff %v", out.Version, pretty.Diff(job2, out)) 266 } 267 } 268 269 func TestJobEndpoint_Register_Periodic(t *testing.T) { 270 t.Parallel() 271 s1 := testServer(t, func(c *Config) { 272 c.NumSchedulers = 0 // Prevent automatic dequeue 273 }) 274 defer s1.Shutdown() 275 codec := rpcClient(t, s1) 276 testutil.WaitForLeader(t, s1.RPC) 277 278 // Create the register request for a periodic job. 279 job := mock.PeriodicJob() 280 req := &structs.JobRegisterRequest{ 281 Job: job, 282 WriteRequest: structs.WriteRequest{Region: "global"}, 283 } 284 285 // Fetch the response 286 var resp structs.JobRegisterResponse 287 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 288 t.Fatalf("err: %v", err) 289 } 290 if resp.JobModifyIndex == 0 { 291 t.Fatalf("bad index: %d", resp.Index) 292 } 293 294 // Check for the node in the FSM 295 state := s1.fsm.State() 296 ws := memdb.NewWatchSet() 297 out, err := state.JobByID(ws, job.ID) 298 if err != nil { 299 t.Fatalf("err: %v", err) 300 } 301 if out == nil { 302 t.Fatalf("expected job") 303 } 304 if out.CreateIndex != resp.JobModifyIndex { 305 t.Fatalf("index mis-match") 306 } 307 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 308 expectedServiceName := "web-frontend" 309 if serviceName != expectedServiceName { 310 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 311 } 312 313 if resp.EvalID != "" { 314 t.Fatalf("Register created an eval for a periodic job") 315 } 316 } 317 318 func TestJobEndpoint_Register_ParameterizedJob(t *testing.T) { 319 t.Parallel() 320 s1 := testServer(t, func(c *Config) { 321 c.NumSchedulers = 0 // Prevent automatic dequeue 322 }) 323 defer s1.Shutdown() 324 codec := rpcClient(t, s1) 325 testutil.WaitForLeader(t, s1.RPC) 326 327 // Create the register request for a parameterized job. 328 job := mock.Job() 329 job.Type = structs.JobTypeBatch 330 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 331 req := &structs.JobRegisterRequest{ 332 Job: job, 333 WriteRequest: structs.WriteRequest{Region: "global"}, 334 } 335 336 // Fetch the response 337 var resp structs.JobRegisterResponse 338 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 339 t.Fatalf("err: %v", err) 340 } 341 if resp.JobModifyIndex == 0 { 342 t.Fatalf("bad index: %d", resp.Index) 343 } 344 345 // Check for the job in the FSM 346 state := s1.fsm.State() 347 ws := memdb.NewWatchSet() 348 out, err := state.JobByID(ws, job.ID) 349 if err != nil { 350 t.Fatalf("err: %v", err) 351 } 352 if out == nil { 353 t.Fatalf("expected job") 354 } 355 if out.CreateIndex != resp.JobModifyIndex { 356 t.Fatalf("index mis-match") 357 } 358 if resp.EvalID != "" { 359 t.Fatalf("Register created an eval for a parameterized job") 360 } 361 } 362 363 func TestJobEndpoint_Register_EnforceIndex(t *testing.T) { 364 t.Parallel() 365 s1 := testServer(t, func(c *Config) { 366 c.NumSchedulers = 0 // Prevent automatic dequeue 367 }) 368 defer s1.Shutdown() 369 codec := rpcClient(t, s1) 370 testutil.WaitForLeader(t, s1.RPC) 371 372 // Create the register request and enforcing an incorrect index 373 job := mock.Job() 374 req := &structs.JobRegisterRequest{ 375 Job: job, 376 EnforceIndex: true, 377 JobModifyIndex: 100, // Not registered yet so not possible 378 WriteRequest: structs.WriteRequest{Region: "global"}, 379 } 380 381 // Fetch the response 382 var resp structs.JobRegisterResponse 383 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 384 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 385 t.Fatalf("expected enforcement error") 386 } 387 388 // Create the register request and enforcing it is new 389 req = &structs.JobRegisterRequest{ 390 Job: job, 391 EnforceIndex: true, 392 JobModifyIndex: 0, 393 WriteRequest: structs.WriteRequest{Region: "global"}, 394 } 395 396 // Fetch the response 397 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 398 t.Fatalf("err: %v", err) 399 } 400 if resp.Index == 0 { 401 t.Fatalf("bad index: %d", resp.Index) 402 } 403 404 curIndex := resp.JobModifyIndex 405 406 // Check for the node in the FSM 407 state := s1.fsm.State() 408 ws := memdb.NewWatchSet() 409 out, err := state.JobByID(ws, job.ID) 410 if err != nil { 411 t.Fatalf("err: %v", err) 412 } 413 if out == nil { 414 t.Fatalf("expected job") 415 } 416 if out.CreateIndex != resp.JobModifyIndex { 417 t.Fatalf("index mis-match") 418 } 419 420 // Reregister request and enforcing it be a new job 421 req = &structs.JobRegisterRequest{ 422 Job: job, 423 EnforceIndex: true, 424 JobModifyIndex: 0, 425 WriteRequest: structs.WriteRequest{Region: "global"}, 426 } 427 428 // Fetch the response 429 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 430 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 431 t.Fatalf("expected enforcement error") 432 } 433 434 // Reregister request and enforcing it be at an incorrect index 435 req = &structs.JobRegisterRequest{ 436 Job: job, 437 EnforceIndex: true, 438 JobModifyIndex: curIndex - 1, 439 WriteRequest: structs.WriteRequest{Region: "global"}, 440 } 441 442 // Fetch the response 443 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 444 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 445 t.Fatalf("expected enforcement error") 446 } 447 448 // Reregister request and enforcing it be at the correct index 449 job.Priority = job.Priority + 1 450 req = &structs.JobRegisterRequest{ 451 Job: job, 452 EnforceIndex: true, 453 JobModifyIndex: curIndex, 454 WriteRequest: structs.WriteRequest{Region: "global"}, 455 } 456 457 // Fetch the response 458 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 459 t.Fatalf("err: %v", err) 460 } 461 if resp.Index == 0 { 462 t.Fatalf("bad index: %d", resp.Index) 463 } 464 465 out, err = state.JobByID(ws, job.ID) 466 if err != nil { 467 t.Fatalf("err: %v", err) 468 } 469 if out == nil { 470 t.Fatalf("expected job") 471 } 472 if out.Priority != job.Priority { 473 t.Fatalf("priority mis-match") 474 } 475 } 476 477 func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) { 478 t.Parallel() 479 s1 := testServer(t, func(c *Config) { 480 c.NumSchedulers = 0 // Prevent automatic dequeue 481 f := false 482 c.VaultConfig.Enabled = &f 483 }) 484 defer s1.Shutdown() 485 codec := rpcClient(t, s1) 486 testutil.WaitForLeader(t, s1.RPC) 487 488 // Create the register request with a job asking for a vault policy 489 job := mock.Job() 490 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 491 Policies: []string{"foo"}, 492 ChangeMode: structs.VaultChangeModeRestart, 493 } 494 req := &structs.JobRegisterRequest{ 495 Job: job, 496 WriteRequest: structs.WriteRequest{Region: "global"}, 497 } 498 499 // Fetch the response 500 var resp structs.JobRegisterResponse 501 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 502 if err == nil || !strings.Contains(err.Error(), "Vault not enabled") { 503 t.Fatalf("expected Vault not enabled error: %v", err) 504 } 505 } 506 507 func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) { 508 t.Parallel() 509 s1 := testServer(t, func(c *Config) { 510 c.NumSchedulers = 0 // Prevent automatic dequeue 511 }) 512 defer s1.Shutdown() 513 codec := rpcClient(t, s1) 514 testutil.WaitForLeader(t, s1.RPC) 515 516 // Enable vault and allow authenticated 517 tr := true 518 s1.config.VaultConfig.Enabled = &tr 519 s1.config.VaultConfig.AllowUnauthenticated = &tr 520 521 // Replace the Vault Client on the server 522 s1.vault = &TestVaultClient{} 523 524 // Create the register request with a job asking for a vault policy 525 job := mock.Job() 526 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 527 Policies: []string{"foo"}, 528 ChangeMode: structs.VaultChangeModeRestart, 529 } 530 req := &structs.JobRegisterRequest{ 531 Job: job, 532 WriteRequest: structs.WriteRequest{Region: "global"}, 533 } 534 535 // Fetch the response 536 var resp structs.JobRegisterResponse 537 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 538 if err != nil { 539 t.Fatalf("bad: %v", err) 540 } 541 542 // Check for the job in the FSM 543 state := s1.fsm.State() 544 ws := memdb.NewWatchSet() 545 out, err := state.JobByID(ws, job.ID) 546 if err != nil { 547 t.Fatalf("err: %v", err) 548 } 549 if out == nil { 550 t.Fatalf("expected job") 551 } 552 if out.CreateIndex != resp.JobModifyIndex { 553 t.Fatalf("index mis-match") 554 } 555 } 556 557 func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) { 558 t.Parallel() 559 s1 := testServer(t, func(c *Config) { 560 c.NumSchedulers = 0 // Prevent automatic dequeue 561 }) 562 defer s1.Shutdown() 563 codec := rpcClient(t, s1) 564 testutil.WaitForLeader(t, s1.RPC) 565 566 // Enable vault 567 tr, f := true, false 568 s1.config.VaultConfig.Enabled = &tr 569 s1.config.VaultConfig.AllowUnauthenticated = &f 570 571 // Replace the Vault Client on the server 572 s1.vault = &TestVaultClient{} 573 574 // Create the register request with a job asking for a vault policy but 575 // don't send a Vault token 576 job := mock.Job() 577 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 578 Policies: []string{"foo"}, 579 ChangeMode: structs.VaultChangeModeRestart, 580 } 581 req := &structs.JobRegisterRequest{ 582 Job: job, 583 WriteRequest: structs.WriteRequest{Region: "global"}, 584 } 585 586 // Fetch the response 587 var resp structs.JobRegisterResponse 588 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 589 if err == nil || !strings.Contains(err.Error(), "missing Vault Token") { 590 t.Fatalf("expected Vault not enabled error: %v", err) 591 } 592 } 593 594 func TestJobEndpoint_Register_Vault_Policies(t *testing.T) { 595 t.Parallel() 596 s1 := testServer(t, func(c *Config) { 597 c.NumSchedulers = 0 // Prevent automatic dequeue 598 }) 599 defer s1.Shutdown() 600 codec := rpcClient(t, s1) 601 testutil.WaitForLeader(t, s1.RPC) 602 603 // Enable vault 604 tr, f := true, false 605 s1.config.VaultConfig.Enabled = &tr 606 s1.config.VaultConfig.AllowUnauthenticated = &f 607 608 // Replace the Vault Client on the server 609 tvc := &TestVaultClient{} 610 s1.vault = tvc 611 612 // Add three tokens: one that allows the requesting policy, one that does 613 // not and one that returns an error 614 policy := "foo" 615 616 badToken := structs.GenerateUUID() 617 badPolicies := []string{"a", "b", "c"} 618 tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies) 619 620 goodToken := structs.GenerateUUID() 621 goodPolicies := []string{"foo", "bar", "baz"} 622 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 623 624 rootToken := structs.GenerateUUID() 625 rootPolicies := []string{"root"} 626 tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies) 627 628 errToken := structs.GenerateUUID() 629 expectedErr := fmt.Errorf("return errors from vault") 630 tvc.SetLookupTokenError(errToken, expectedErr) 631 632 // Create the register request with a job asking for a vault policy but 633 // send the bad Vault token 634 job := mock.Job() 635 job.VaultToken = badToken 636 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 637 Policies: []string{policy}, 638 ChangeMode: structs.VaultChangeModeRestart, 639 } 640 req := &structs.JobRegisterRequest{ 641 Job: job, 642 WriteRequest: structs.WriteRequest{Region: "global"}, 643 } 644 645 // Fetch the response 646 var resp structs.JobRegisterResponse 647 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 648 if err == nil || !strings.Contains(err.Error(), 649 "doesn't allow access to the following policies: "+policy) { 650 t.Fatalf("expected permission denied error: %v", err) 651 } 652 653 // Use the err token 654 job.VaultToken = errToken 655 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 656 if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) { 657 t.Fatalf("expected permission denied error: %v", err) 658 } 659 660 // Use the good token 661 job.VaultToken = goodToken 662 663 // Fetch the response 664 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 665 t.Fatalf("bad: %v", err) 666 } 667 668 // Check for the job in the FSM 669 state := s1.fsm.State() 670 ws := memdb.NewWatchSet() 671 out, err := state.JobByID(ws, job.ID) 672 if err != nil { 673 t.Fatalf("err: %v", err) 674 } 675 if out == nil { 676 t.Fatalf("expected job") 677 } 678 if out.CreateIndex != resp.JobModifyIndex { 679 t.Fatalf("index mis-match") 680 } 681 if out.VaultToken != "" { 682 t.Fatalf("vault token not cleared") 683 } 684 685 // Check that an implicit constraint was created 686 constraints := out.TaskGroups[0].Constraints 687 if l := len(constraints); l != 1 { 688 t.Fatalf("Unexpected number of tests: %v", l) 689 } 690 691 if !constraints[0].Equal(vaultConstraint) { 692 t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint) 693 } 694 695 // Create the register request with another job asking for a vault policy but 696 // send the root Vault token 697 job2 := mock.Job() 698 job2.VaultToken = rootToken 699 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 700 Policies: []string{policy}, 701 ChangeMode: structs.VaultChangeModeRestart, 702 } 703 req = &structs.JobRegisterRequest{ 704 Job: job2, 705 WriteRequest: structs.WriteRequest{Region: "global"}, 706 } 707 708 // Fetch the response 709 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 710 t.Fatalf("bad: %v", err) 711 } 712 713 // Check for the job in the FSM 714 out, err = state.JobByID(ws, job2.ID) 715 if err != nil { 716 t.Fatalf("err: %v", err) 717 } 718 if out == nil { 719 t.Fatalf("expected job") 720 } 721 if out.CreateIndex != resp.JobModifyIndex { 722 t.Fatalf("index mis-match") 723 } 724 if out.VaultToken != "" { 725 t.Fatalf("vault token not cleared") 726 } 727 } 728 729 func TestJobEndpoint_Revert(t *testing.T) { 730 t.Parallel() 731 s1 := testServer(t, func(c *Config) { 732 c.NumSchedulers = 0 // Prevent automatic dequeue 733 }) 734 defer s1.Shutdown() 735 codec := rpcClient(t, s1) 736 testutil.WaitForLeader(t, s1.RPC) 737 738 // Create the initial register request 739 job := mock.Job() 740 job.Priority = 100 741 req := &structs.JobRegisterRequest{ 742 Job: job, 743 WriteRequest: structs.WriteRequest{Region: "global"}, 744 } 745 746 // Fetch the response 747 var resp structs.JobRegisterResponse 748 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 749 t.Fatalf("err: %v", err) 750 } 751 if resp.Index == 0 { 752 t.Fatalf("bad index: %d", resp.Index) 753 } 754 755 // Reregister again to get another version 756 job2 := job.Copy() 757 job2.Priority = 1 758 req = &structs.JobRegisterRequest{ 759 Job: job2, 760 WriteRequest: structs.WriteRequest{Region: "global"}, 761 } 762 763 // Fetch the response 764 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 765 t.Fatalf("err: %v", err) 766 } 767 if resp.Index == 0 { 768 t.Fatalf("bad index: %d", resp.Index) 769 } 770 771 // Create revert request and enforcing it be at an incorrect version 772 revertReq := &structs.JobRevertRequest{ 773 JobID: job.ID, 774 JobVersion: 0, 775 EnforcePriorVersion: helper.Uint64ToPtr(10), 776 WriteRequest: structs.WriteRequest{Region: "global"}, 777 } 778 779 // Fetch the response 780 err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp) 781 if err == nil || !strings.Contains(err.Error(), "enforcing version 10") { 782 t.Fatalf("expected enforcement error") 783 } 784 785 // Create revert request and enforcing it be at the current version 786 revertReq = &structs.JobRevertRequest{ 787 JobID: job.ID, 788 JobVersion: 1, 789 WriteRequest: structs.WriteRequest{Region: "global"}, 790 } 791 792 // Fetch the response 793 err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp) 794 if err == nil || !strings.Contains(err.Error(), "current version") { 795 t.Fatalf("expected current version err: %v", err) 796 } 797 798 // Create revert request and enforcing it be at version 1 799 revertReq = &structs.JobRevertRequest{ 800 JobID: job.ID, 801 JobVersion: 0, 802 EnforcePriorVersion: helper.Uint64ToPtr(1), 803 WriteRequest: structs.WriteRequest{Region: "global"}, 804 } 805 806 // Fetch the response 807 if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil { 808 t.Fatalf("err: %v", err) 809 } 810 if resp.Index == 0 { 811 t.Fatalf("bad index: %d", resp.Index) 812 } 813 if resp.EvalID == "" || resp.EvalCreateIndex == 0 { 814 t.Fatalf("bad created eval: %+v", resp) 815 } 816 if resp.JobModifyIndex == 0 { 817 t.Fatalf("bad job modify index: %d", resp.JobModifyIndex) 818 } 819 820 // Create revert request and don't enforce. We are at version 2 but it is 821 // the same as version 0 822 revertReq = &structs.JobRevertRequest{ 823 JobID: job.ID, 824 JobVersion: 0, 825 WriteRequest: structs.WriteRequest{Region: "global"}, 826 } 827 828 // Fetch the response 829 if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil { 830 t.Fatalf("err: %v", err) 831 } 832 if resp.Index == 0 { 833 t.Fatalf("bad index: %d", resp.Index) 834 } 835 if resp.EvalID == "" || resp.EvalCreateIndex == 0 { 836 t.Fatalf("bad created eval: %+v", resp) 837 } 838 if resp.JobModifyIndex == 0 { 839 t.Fatalf("bad job modify index: %d", resp.JobModifyIndex) 840 } 841 842 // Check that the job is at the correct version and that the eval was 843 // created 844 state := s1.fsm.State() 845 ws := memdb.NewWatchSet() 846 out, err := state.JobByID(ws, job.ID) 847 if err != nil { 848 t.Fatalf("err: %v", err) 849 } 850 if out == nil { 851 t.Fatalf("expected job") 852 } 853 if out.Priority != job.Priority { 854 t.Fatalf("priority mis-match") 855 } 856 if out.Version != 2 { 857 t.Fatalf("got version %d; want %d", out.Version, 2) 858 } 859 860 eout, err := state.EvalByID(ws, resp.EvalID) 861 if err != nil { 862 t.Fatalf("err: %v", err) 863 } 864 if eout == nil { 865 t.Fatalf("expected eval") 866 } 867 if eout.JobID != job.ID { 868 t.Fatalf("job id mis-match") 869 } 870 871 versions, err := state.JobVersionsByID(ws, job.ID) 872 if err != nil { 873 t.Fatalf("err: %v", err) 874 } 875 if len(versions) != 3 { 876 t.Fatalf("got %d versions; want %d", len(versions), 3) 877 } 878 } 879 880 func TestJobEndpoint_Stable(t *testing.T) { 881 t.Parallel() 882 s1 := testServer(t, func(c *Config) { 883 c.NumSchedulers = 0 // Prevent automatic dequeue 884 }) 885 defer s1.Shutdown() 886 codec := rpcClient(t, s1) 887 testutil.WaitForLeader(t, s1.RPC) 888 889 // Create the initial register request 890 job := mock.Job() 891 req := &structs.JobRegisterRequest{ 892 Job: job, 893 WriteRequest: structs.WriteRequest{Region: "global"}, 894 } 895 896 // Fetch the response 897 var resp structs.JobRegisterResponse 898 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 899 t.Fatalf("err: %v", err) 900 } 901 if resp.Index == 0 { 902 t.Fatalf("bad index: %d", resp.Index) 903 } 904 905 // Create stablility request 906 stableReq := &structs.JobStabilityRequest{ 907 JobID: job.ID, 908 JobVersion: 0, 909 Stable: true, 910 WriteRequest: structs.WriteRequest{Region: "global"}, 911 } 912 913 // Fetch the response 914 var stableResp structs.JobStabilityResponse 915 if err := msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &stableResp); err != nil { 916 t.Fatalf("err: %v", err) 917 } 918 if stableResp.Index == 0 { 919 t.Fatalf("bad index: %d", resp.Index) 920 } 921 922 // Check that the job is marked stable 923 state := s1.fsm.State() 924 ws := memdb.NewWatchSet() 925 out, err := state.JobByID(ws, job.ID) 926 if err != nil { 927 t.Fatalf("err: %v", err) 928 } 929 if out == nil { 930 t.Fatalf("expected job") 931 } 932 if !out.Stable { 933 t.Fatalf("Job is not marked stable") 934 } 935 } 936 937 func TestJobEndpoint_Evaluate(t *testing.T) { 938 t.Parallel() 939 s1 := testServer(t, func(c *Config) { 940 c.NumSchedulers = 0 // Prevent automatic dequeue 941 }) 942 defer s1.Shutdown() 943 codec := rpcClient(t, s1) 944 testutil.WaitForLeader(t, s1.RPC) 945 946 // Create the register request 947 job := mock.Job() 948 req := &structs.JobRegisterRequest{ 949 Job: job, 950 WriteRequest: structs.WriteRequest{Region: "global"}, 951 } 952 953 // Fetch the response 954 var resp structs.JobRegisterResponse 955 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 956 t.Fatalf("err: %v", err) 957 } 958 if resp.Index == 0 { 959 t.Fatalf("bad index: %d", resp.Index) 960 } 961 962 // Force a re-evaluation 963 reEval := &structs.JobEvaluateRequest{ 964 JobID: job.ID, 965 WriteRequest: structs.WriteRequest{Region: "global"}, 966 } 967 968 // Fetch the response 969 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil { 970 t.Fatalf("err: %v", err) 971 } 972 if resp.Index == 0 { 973 t.Fatalf("bad index: %d", resp.Index) 974 } 975 976 // Lookup the evaluation 977 state := s1.fsm.State() 978 ws := memdb.NewWatchSet() 979 eval, err := state.EvalByID(ws, resp.EvalID) 980 if err != nil { 981 t.Fatalf("err: %v", err) 982 } 983 if eval == nil { 984 t.Fatalf("expected eval") 985 } 986 if eval.CreateIndex != resp.EvalCreateIndex { 987 t.Fatalf("index mis-match") 988 } 989 990 if eval.Priority != job.Priority { 991 t.Fatalf("bad: %#v", eval) 992 } 993 if eval.Type != job.Type { 994 t.Fatalf("bad: %#v", eval) 995 } 996 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 997 t.Fatalf("bad: %#v", eval) 998 } 999 if eval.JobID != job.ID { 1000 t.Fatalf("bad: %#v", eval) 1001 } 1002 if eval.JobModifyIndex != resp.JobModifyIndex { 1003 t.Fatalf("bad: %#v", eval) 1004 } 1005 if eval.Status != structs.EvalStatusPending { 1006 t.Fatalf("bad: %#v", eval) 1007 } 1008 } 1009 1010 func TestJobEndpoint_Evaluate_Periodic(t *testing.T) { 1011 t.Parallel() 1012 s1 := testServer(t, func(c *Config) { 1013 c.NumSchedulers = 0 // Prevent automatic dequeue 1014 }) 1015 defer s1.Shutdown() 1016 codec := rpcClient(t, s1) 1017 testutil.WaitForLeader(t, s1.RPC) 1018 1019 // Create the register request 1020 job := mock.PeriodicJob() 1021 req := &structs.JobRegisterRequest{ 1022 Job: job, 1023 WriteRequest: structs.WriteRequest{Region: "global"}, 1024 } 1025 1026 // Fetch the response 1027 var resp structs.JobRegisterResponse 1028 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1029 t.Fatalf("err: %v", err) 1030 } 1031 if resp.JobModifyIndex == 0 { 1032 t.Fatalf("bad index: %d", resp.Index) 1033 } 1034 1035 // Force a re-evaluation 1036 reEval := &structs.JobEvaluateRequest{ 1037 JobID: job.ID, 1038 WriteRequest: structs.WriteRequest{Region: "global"}, 1039 } 1040 1041 // Fetch the response 1042 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 1043 t.Fatal("expect an err") 1044 } 1045 } 1046 1047 func TestJobEndpoint_Evaluate_ParameterizedJob(t *testing.T) { 1048 t.Parallel() 1049 s1 := testServer(t, func(c *Config) { 1050 c.NumSchedulers = 0 // Prevent automatic dequeue 1051 }) 1052 defer s1.Shutdown() 1053 codec := rpcClient(t, s1) 1054 testutil.WaitForLeader(t, s1.RPC) 1055 1056 // Create the register request 1057 job := mock.Job() 1058 job.Type = structs.JobTypeBatch 1059 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1060 req := &structs.JobRegisterRequest{ 1061 Job: job, 1062 WriteRequest: structs.WriteRequest{Region: "global"}, 1063 } 1064 1065 // Fetch the response 1066 var resp structs.JobRegisterResponse 1067 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1068 t.Fatalf("err: %v", err) 1069 } 1070 if resp.JobModifyIndex == 0 { 1071 t.Fatalf("bad index: %d", resp.Index) 1072 } 1073 1074 // Force a re-evaluation 1075 reEval := &structs.JobEvaluateRequest{ 1076 JobID: job.ID, 1077 WriteRequest: structs.WriteRequest{Region: "global"}, 1078 } 1079 1080 // Fetch the response 1081 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 1082 t.Fatal("expect an err") 1083 } 1084 } 1085 1086 func TestJobEndpoint_Deregister(t *testing.T) { 1087 t.Parallel() 1088 s1 := testServer(t, func(c *Config) { 1089 c.NumSchedulers = 0 // Prevent automatic dequeue 1090 }) 1091 defer s1.Shutdown() 1092 codec := rpcClient(t, s1) 1093 testutil.WaitForLeader(t, s1.RPC) 1094 1095 // Create the register request 1096 job := mock.Job() 1097 reg := &structs.JobRegisterRequest{ 1098 Job: job, 1099 WriteRequest: structs.WriteRequest{Region: "global"}, 1100 } 1101 1102 // Fetch the response 1103 var resp structs.JobRegisterResponse 1104 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1105 t.Fatalf("err: %v", err) 1106 } 1107 1108 // Deregister but don't purge 1109 dereg := &structs.JobDeregisterRequest{ 1110 JobID: job.ID, 1111 Purge: false, 1112 WriteRequest: structs.WriteRequest{Region: "global"}, 1113 } 1114 var resp2 structs.JobDeregisterResponse 1115 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1116 t.Fatalf("err: %v", err) 1117 } 1118 if resp2.Index == 0 { 1119 t.Fatalf("bad index: %d", resp2.Index) 1120 } 1121 1122 // Check for the job in the FSM 1123 ws := memdb.NewWatchSet() 1124 state := s1.fsm.State() 1125 out, err := state.JobByID(ws, job.ID) 1126 if err != nil { 1127 t.Fatalf("err: %v", err) 1128 } 1129 if out == nil { 1130 t.Fatalf("job purged") 1131 } 1132 if !out.Stop { 1133 t.Fatalf("job not stopped") 1134 } 1135 1136 // Lookup the evaluation 1137 eval, err := state.EvalByID(ws, resp2.EvalID) 1138 if err != nil { 1139 t.Fatalf("err: %v", err) 1140 } 1141 if eval == nil { 1142 t.Fatalf("expected eval") 1143 } 1144 if eval.CreateIndex != resp2.EvalCreateIndex { 1145 t.Fatalf("index mis-match") 1146 } 1147 1148 if eval.Priority != structs.JobDefaultPriority { 1149 t.Fatalf("bad: %#v", eval) 1150 } 1151 if eval.Type != structs.JobTypeService { 1152 t.Fatalf("bad: %#v", eval) 1153 } 1154 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 1155 t.Fatalf("bad: %#v", eval) 1156 } 1157 if eval.JobID != job.ID { 1158 t.Fatalf("bad: %#v", eval) 1159 } 1160 if eval.JobModifyIndex != resp2.JobModifyIndex { 1161 t.Fatalf("bad: %#v", eval) 1162 } 1163 if eval.Status != structs.EvalStatusPending { 1164 t.Fatalf("bad: %#v", eval) 1165 } 1166 1167 // Deregister and purge 1168 dereg2 := &structs.JobDeregisterRequest{ 1169 JobID: job.ID, 1170 Purge: true, 1171 WriteRequest: structs.WriteRequest{Region: "global"}, 1172 } 1173 var resp3 structs.JobDeregisterResponse 1174 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg2, &resp3); err != nil { 1175 t.Fatalf("err: %v", err) 1176 } 1177 if resp3.Index == 0 { 1178 t.Fatalf("bad index: %d", resp3.Index) 1179 } 1180 1181 // Check for the job in the FSM 1182 out, err = state.JobByID(ws, job.ID) 1183 if err != nil { 1184 t.Fatalf("err: %v", err) 1185 } 1186 if out != nil { 1187 t.Fatalf("unexpected job") 1188 } 1189 1190 // Lookup the evaluation 1191 eval, err = state.EvalByID(ws, resp3.EvalID) 1192 if err != nil { 1193 t.Fatalf("err: %v", err) 1194 } 1195 if eval == nil { 1196 t.Fatalf("expected eval") 1197 } 1198 if eval.CreateIndex != resp3.EvalCreateIndex { 1199 t.Fatalf("index mis-match") 1200 } 1201 1202 if eval.Priority != structs.JobDefaultPriority { 1203 t.Fatalf("bad: %#v", eval) 1204 } 1205 if eval.Type != structs.JobTypeService { 1206 t.Fatalf("bad: %#v", eval) 1207 } 1208 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 1209 t.Fatalf("bad: %#v", eval) 1210 } 1211 if eval.JobID != job.ID { 1212 t.Fatalf("bad: %#v", eval) 1213 } 1214 if eval.JobModifyIndex != resp3.JobModifyIndex { 1215 t.Fatalf("bad: %#v", eval) 1216 } 1217 if eval.Status != structs.EvalStatusPending { 1218 t.Fatalf("bad: %#v", eval) 1219 } 1220 } 1221 1222 func TestJobEndpoint_Deregister_NonExistent(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 // Deregister 1232 jobID := "foo" 1233 dereg := &structs.JobDeregisterRequest{ 1234 JobID: jobID, 1235 WriteRequest: structs.WriteRequest{Region: "global"}, 1236 } 1237 var resp2 structs.JobDeregisterResponse 1238 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1239 t.Fatalf("err: %v", err) 1240 } 1241 if resp2.JobModifyIndex == 0 { 1242 t.Fatalf("bad index: %d", resp2.Index) 1243 } 1244 1245 // Lookup the evaluation 1246 state := s1.fsm.State() 1247 ws := memdb.NewWatchSet() 1248 eval, err := state.EvalByID(ws, resp2.EvalID) 1249 if err != nil { 1250 t.Fatalf("err: %v", err) 1251 } 1252 if eval == nil { 1253 t.Fatalf("expected eval") 1254 } 1255 if eval.CreateIndex != resp2.EvalCreateIndex { 1256 t.Fatalf("index mis-match") 1257 } 1258 1259 if eval.Priority != structs.JobDefaultPriority { 1260 t.Fatalf("bad: %#v", eval) 1261 } 1262 if eval.Type != structs.JobTypeService { 1263 t.Fatalf("bad: %#v", eval) 1264 } 1265 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 1266 t.Fatalf("bad: %#v", eval) 1267 } 1268 if eval.JobID != jobID { 1269 t.Fatalf("bad: %#v", eval) 1270 } 1271 if eval.JobModifyIndex != resp2.JobModifyIndex { 1272 t.Fatalf("bad: %#v", eval) 1273 } 1274 if eval.Status != structs.EvalStatusPending { 1275 t.Fatalf("bad: %#v", eval) 1276 } 1277 } 1278 1279 func TestJobEndpoint_Deregister_Periodic(t *testing.T) { 1280 t.Parallel() 1281 s1 := testServer(t, func(c *Config) { 1282 c.NumSchedulers = 0 // Prevent automatic dequeue 1283 }) 1284 defer s1.Shutdown() 1285 codec := rpcClient(t, s1) 1286 testutil.WaitForLeader(t, s1.RPC) 1287 1288 // Create the register request 1289 job := mock.PeriodicJob() 1290 reg := &structs.JobRegisterRequest{ 1291 Job: job, 1292 WriteRequest: structs.WriteRequest{Region: "global"}, 1293 } 1294 1295 // Fetch the response 1296 var resp structs.JobRegisterResponse 1297 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1298 t.Fatalf("err: %v", err) 1299 } 1300 1301 // Deregister 1302 dereg := &structs.JobDeregisterRequest{ 1303 JobID: job.ID, 1304 Purge: true, 1305 WriteRequest: structs.WriteRequest{Region: "global"}, 1306 } 1307 var resp2 structs.JobDeregisterResponse 1308 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1309 t.Fatalf("err: %v", err) 1310 } 1311 if resp2.JobModifyIndex == 0 { 1312 t.Fatalf("bad index: %d", resp2.Index) 1313 } 1314 1315 // Check for the node in the FSM 1316 state := s1.fsm.State() 1317 ws := memdb.NewWatchSet() 1318 out, err := state.JobByID(ws, job.ID) 1319 if err != nil { 1320 t.Fatalf("err: %v", err) 1321 } 1322 if out != nil { 1323 t.Fatalf("unexpected job") 1324 } 1325 1326 if resp.EvalID != "" { 1327 t.Fatalf("Deregister created an eval for a periodic job") 1328 } 1329 } 1330 1331 func TestJobEndpoint_Deregister_ParameterizedJob(t *testing.T) { 1332 t.Parallel() 1333 s1 := testServer(t, func(c *Config) { 1334 c.NumSchedulers = 0 // Prevent automatic dequeue 1335 }) 1336 defer s1.Shutdown() 1337 codec := rpcClient(t, s1) 1338 testutil.WaitForLeader(t, s1.RPC) 1339 1340 // Create the register request 1341 job := mock.Job() 1342 job.Type = structs.JobTypeBatch 1343 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1344 reg := &structs.JobRegisterRequest{ 1345 Job: job, 1346 WriteRequest: structs.WriteRequest{Region: "global"}, 1347 } 1348 1349 // Fetch the response 1350 var resp structs.JobRegisterResponse 1351 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1352 t.Fatalf("err: %v", err) 1353 } 1354 1355 // Deregister 1356 dereg := &structs.JobDeregisterRequest{ 1357 JobID: job.ID, 1358 Purge: true, 1359 WriteRequest: structs.WriteRequest{Region: "global"}, 1360 } 1361 var resp2 structs.JobDeregisterResponse 1362 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1363 t.Fatalf("err: %v", err) 1364 } 1365 if resp2.JobModifyIndex == 0 { 1366 t.Fatalf("bad index: %d", resp2.Index) 1367 } 1368 1369 // Check for the node in the FSM 1370 state := s1.fsm.State() 1371 ws := memdb.NewWatchSet() 1372 out, err := state.JobByID(ws, job.ID) 1373 if err != nil { 1374 t.Fatalf("err: %v", err) 1375 } 1376 if out != nil { 1377 t.Fatalf("unexpected job") 1378 } 1379 1380 if resp.EvalID != "" { 1381 t.Fatalf("Deregister created an eval for a parameterized job") 1382 } 1383 } 1384 1385 func TestJobEndpoint_GetJob(t *testing.T) { 1386 t.Parallel() 1387 s1 := testServer(t, nil) 1388 defer s1.Shutdown() 1389 codec := rpcClient(t, s1) 1390 testutil.WaitForLeader(t, s1.RPC) 1391 1392 // Create the register request 1393 job := mock.Job() 1394 reg := &structs.JobRegisterRequest{ 1395 Job: job, 1396 WriteRequest: structs.WriteRequest{Region: "global"}, 1397 } 1398 1399 // Fetch the response 1400 var resp structs.JobRegisterResponse 1401 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1402 t.Fatalf("err: %v", err) 1403 } 1404 job.CreateIndex = resp.JobModifyIndex 1405 job.ModifyIndex = resp.JobModifyIndex 1406 job.JobModifyIndex = resp.JobModifyIndex 1407 1408 // Lookup the job 1409 get := &structs.JobSpecificRequest{ 1410 JobID: job.ID, 1411 QueryOptions: structs.QueryOptions{Region: "global"}, 1412 } 1413 var resp2 structs.SingleJobResponse 1414 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 1415 t.Fatalf("err: %v", err) 1416 } 1417 if resp2.Index != resp.JobModifyIndex { 1418 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 1419 } 1420 1421 // Make a copy of the origin job and change the service name so that we can 1422 // do a deep equal with the response from the GET JOB Api 1423 j := job 1424 j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend" 1425 for tgix, tg := range j.TaskGroups { 1426 for tidx, t := range tg.Tasks { 1427 for sidx, service := range t.Services { 1428 for cidx, check := range service.Checks { 1429 check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name 1430 } 1431 } 1432 } 1433 } 1434 1435 // Clear the submit times 1436 j.SubmitTime = 0 1437 resp2.Job.SubmitTime = 0 1438 1439 if !reflect.DeepEqual(j, resp2.Job) { 1440 t.Fatalf("bad: %#v %#v", job, resp2.Job) 1441 } 1442 1443 // Lookup non-existing job 1444 get.JobID = "foobarbaz" 1445 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 1446 t.Fatalf("err: %v", err) 1447 } 1448 if resp2.Index != resp.JobModifyIndex { 1449 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 1450 } 1451 if resp2.Job != nil { 1452 t.Fatalf("unexpected job") 1453 } 1454 } 1455 1456 func TestJobEndpoint_GetJob_Blocking(t *testing.T) { 1457 t.Parallel() 1458 s1 := testServer(t, nil) 1459 defer s1.Shutdown() 1460 state := s1.fsm.State() 1461 codec := rpcClient(t, s1) 1462 testutil.WaitForLeader(t, s1.RPC) 1463 1464 // Create the jobs 1465 job1 := mock.Job() 1466 job2 := mock.Job() 1467 1468 // Upsert a job we are not interested in first. 1469 time.AfterFunc(100*time.Millisecond, func() { 1470 if err := state.UpsertJob(100, job1); err != nil { 1471 t.Fatalf("err: %v", err) 1472 } 1473 }) 1474 1475 // Upsert another job later which should trigger the watch. 1476 time.AfterFunc(200*time.Millisecond, func() { 1477 if err := state.UpsertJob(200, job2); err != nil { 1478 t.Fatalf("err: %v", err) 1479 } 1480 }) 1481 1482 req := &structs.JobSpecificRequest{ 1483 JobID: job2.ID, 1484 QueryOptions: structs.QueryOptions{ 1485 Region: "global", 1486 MinQueryIndex: 150, 1487 }, 1488 } 1489 start := time.Now() 1490 var resp structs.SingleJobResponse 1491 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil { 1492 t.Fatalf("err: %v", err) 1493 } 1494 1495 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1496 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1497 } 1498 if resp.Index != 200 { 1499 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1500 } 1501 if resp.Job == nil || resp.Job.ID != job2.ID { 1502 t.Fatalf("bad: %#v", resp.Job) 1503 } 1504 1505 // Job delete fires watches 1506 time.AfterFunc(100*time.Millisecond, func() { 1507 if err := state.DeleteJob(300, job2.ID); err != nil { 1508 t.Fatalf("err: %v", err) 1509 } 1510 }) 1511 1512 req.QueryOptions.MinQueryIndex = 250 1513 start = time.Now() 1514 1515 var resp2 structs.SingleJobResponse 1516 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil { 1517 t.Fatalf("err: %v", err) 1518 } 1519 1520 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1521 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1522 } 1523 if resp2.Index != 300 { 1524 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 1525 } 1526 if resp2.Job != nil { 1527 t.Fatalf("bad: %#v", resp2.Job) 1528 } 1529 } 1530 1531 func TestJobEndpoint_GetJobVersions(t *testing.T) { 1532 t.Parallel() 1533 s1 := testServer(t, nil) 1534 defer s1.Shutdown() 1535 codec := rpcClient(t, s1) 1536 testutil.WaitForLeader(t, s1.RPC) 1537 1538 // Create the register request 1539 job := mock.Job() 1540 job.Priority = 88 1541 reg := &structs.JobRegisterRequest{ 1542 Job: job, 1543 WriteRequest: structs.WriteRequest{Region: "global"}, 1544 } 1545 1546 // Fetch the response 1547 var resp structs.JobRegisterResponse 1548 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1549 t.Fatalf("err: %v", err) 1550 } 1551 1552 // Register the job again to create another version 1553 job.Priority = 100 1554 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1555 t.Fatalf("err: %v", err) 1556 } 1557 1558 // Lookup the job 1559 get := &structs.JobVersionsRequest{ 1560 JobID: job.ID, 1561 QueryOptions: structs.QueryOptions{Region: "global"}, 1562 } 1563 var versionsResp structs.JobVersionsResponse 1564 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil { 1565 t.Fatalf("err: %v", err) 1566 } 1567 if versionsResp.Index != resp.JobModifyIndex { 1568 t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index) 1569 } 1570 1571 // Make sure there are two job versions 1572 versions := versionsResp.Versions 1573 if l := len(versions); l != 2 { 1574 t.Fatalf("Got %d versions; want 2", l) 1575 } 1576 1577 if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 1 { 1578 t.Fatalf("bad: %+v", v) 1579 } 1580 if v := versions[1]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 { 1581 t.Fatalf("bad: %+v", v) 1582 } 1583 1584 // Lookup non-existing job 1585 get.JobID = "foobarbaz" 1586 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil { 1587 t.Fatalf("err: %v", err) 1588 } 1589 if versionsResp.Index != resp.JobModifyIndex { 1590 t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index) 1591 } 1592 if l := len(versionsResp.Versions); l != 0 { 1593 t.Fatalf("unexpected versions: %d", l) 1594 } 1595 } 1596 1597 func TestJobEndpoint_GetJobVersions_Diff(t *testing.T) { 1598 t.Parallel() 1599 s1 := testServer(t, nil) 1600 defer s1.Shutdown() 1601 codec := rpcClient(t, s1) 1602 testutil.WaitForLeader(t, s1.RPC) 1603 1604 // Create the register request 1605 job := mock.Job() 1606 job.Priority = 88 1607 reg := &structs.JobRegisterRequest{ 1608 Job: job, 1609 WriteRequest: structs.WriteRequest{Region: "global"}, 1610 } 1611 1612 // Fetch the response 1613 var resp structs.JobRegisterResponse 1614 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1615 t.Fatalf("err: %v", err) 1616 } 1617 1618 // Register the job again to create another version 1619 job.Priority = 90 1620 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1621 t.Fatalf("err: %v", err) 1622 } 1623 1624 // Register the job again to create another version 1625 job.Priority = 100 1626 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1627 t.Fatalf("err: %v", err) 1628 } 1629 1630 // Lookup the job 1631 get := &structs.JobVersionsRequest{ 1632 JobID: job.ID, 1633 Diffs: true, 1634 QueryOptions: structs.QueryOptions{Region: "global"}, 1635 } 1636 var versionsResp structs.JobVersionsResponse 1637 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil { 1638 t.Fatalf("err: %v", err) 1639 } 1640 if versionsResp.Index != resp.JobModifyIndex { 1641 t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index) 1642 } 1643 1644 // Make sure there are two job versions 1645 versions := versionsResp.Versions 1646 if l := len(versions); l != 3 { 1647 t.Fatalf("Got %d versions; want 3", l) 1648 } 1649 1650 if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 2 { 1651 t.Fatalf("bad: %+v", v) 1652 } 1653 if v := versions[1]; v.Priority != 90 || v.ID != job.ID || v.Version != 1 { 1654 t.Fatalf("bad: %+v", v) 1655 } 1656 if v := versions[2]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 { 1657 t.Fatalf("bad: %+v", v) 1658 } 1659 1660 // Ensure we got diffs 1661 diffs := versionsResp.Diffs 1662 if l := len(diffs); l != 2 { 1663 t.Fatalf("Got %d diffs; want 2", l) 1664 } 1665 d1 := diffs[0] 1666 if len(d1.Fields) != 1 { 1667 t.Fatalf("Got too many diffs: %#v", d1) 1668 } 1669 if d1.Fields[0].Name != "Priority" { 1670 t.Fatalf("Got wrong field: %#v", d1) 1671 } 1672 if d1.Fields[0].Old != "90" && d1.Fields[0].New != "100" { 1673 t.Fatalf("Got wrong field values: %#v", d1) 1674 } 1675 d2 := diffs[1] 1676 if len(d2.Fields) != 1 { 1677 t.Fatalf("Got too many diffs: %#v", d2) 1678 } 1679 if d2.Fields[0].Name != "Priority" { 1680 t.Fatalf("Got wrong field: %#v", d2) 1681 } 1682 if d2.Fields[0].Old != "88" && d1.Fields[0].New != "90" { 1683 t.Fatalf("Got wrong field values: %#v", d2) 1684 } 1685 } 1686 1687 func TestJobEndpoint_GetJobVersions_Blocking(t *testing.T) { 1688 t.Parallel() 1689 s1 := testServer(t, nil) 1690 defer s1.Shutdown() 1691 state := s1.fsm.State() 1692 codec := rpcClient(t, s1) 1693 testutil.WaitForLeader(t, s1.RPC) 1694 1695 // Create the jobs 1696 job1 := mock.Job() 1697 job2 := mock.Job() 1698 job3 := mock.Job() 1699 job3.ID = job2.ID 1700 job3.Priority = 1 1701 1702 // Upsert a job we are not interested in first. 1703 time.AfterFunc(100*time.Millisecond, func() { 1704 if err := state.UpsertJob(100, job1); err != nil { 1705 t.Fatalf("err: %v", err) 1706 } 1707 }) 1708 1709 // Upsert another job later which should trigger the watch. 1710 time.AfterFunc(200*time.Millisecond, func() { 1711 if err := state.UpsertJob(200, job2); err != nil { 1712 t.Fatalf("err: %v", err) 1713 } 1714 }) 1715 1716 req := &structs.JobVersionsRequest{ 1717 JobID: job2.ID, 1718 QueryOptions: structs.QueryOptions{ 1719 Region: "global", 1720 MinQueryIndex: 150, 1721 }, 1722 } 1723 start := time.Now() 1724 var resp structs.JobVersionsResponse 1725 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req, &resp); err != nil { 1726 t.Fatalf("err: %v", err) 1727 } 1728 1729 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1730 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1731 } 1732 if resp.Index != 200 { 1733 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1734 } 1735 if len(resp.Versions) != 1 || resp.Versions[0].ID != job2.ID { 1736 t.Fatalf("bad: %#v", resp.Versions) 1737 } 1738 1739 // Upsert the job again which should trigger the watch. 1740 time.AfterFunc(100*time.Millisecond, func() { 1741 if err := state.UpsertJob(300, job3); err != nil { 1742 t.Fatalf("err: %v", err) 1743 } 1744 }) 1745 1746 req2 := &structs.JobVersionsRequest{ 1747 JobID: job3.ID, 1748 QueryOptions: structs.QueryOptions{ 1749 Region: "global", 1750 MinQueryIndex: 250, 1751 }, 1752 } 1753 var resp2 structs.JobVersionsResponse 1754 start = time.Now() 1755 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req2, &resp2); err != nil { 1756 t.Fatalf("err: %v", err) 1757 } 1758 1759 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1760 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1761 } 1762 if resp2.Index != 300 { 1763 t.Fatalf("Bad index: %d %d", resp.Index, 300) 1764 } 1765 if len(resp2.Versions) != 2 { 1766 t.Fatalf("bad: %#v", resp2.Versions) 1767 } 1768 } 1769 1770 func TestJobEndpoint_GetJobSummary(t *testing.T) { 1771 t.Parallel() 1772 s1 := testServer(t, func(c *Config) { 1773 c.NumSchedulers = 0 // Prevent automatic dequeue 1774 }) 1775 1776 defer s1.Shutdown() 1777 codec := rpcClient(t, s1) 1778 testutil.WaitForLeader(t, s1.RPC) 1779 1780 // Create the register request 1781 job := mock.Job() 1782 reg := &structs.JobRegisterRequest{ 1783 Job: job, 1784 WriteRequest: structs.WriteRequest{Region: "global"}, 1785 } 1786 1787 // Fetch the response 1788 var resp structs.JobRegisterResponse 1789 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1790 t.Fatalf("err: %v", err) 1791 } 1792 job.CreateIndex = resp.JobModifyIndex 1793 job.ModifyIndex = resp.JobModifyIndex 1794 job.JobModifyIndex = resp.JobModifyIndex 1795 1796 // Lookup the job summary 1797 get := &structs.JobSummaryRequest{ 1798 JobID: job.ID, 1799 QueryOptions: structs.QueryOptions{Region: "global"}, 1800 } 1801 var resp2 structs.JobSummaryResponse 1802 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil { 1803 t.Fatalf("err: %v", err) 1804 } 1805 if resp2.Index != resp.JobModifyIndex { 1806 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 1807 } 1808 1809 expectedJobSummary := structs.JobSummary{ 1810 JobID: job.ID, 1811 Summary: map[string]structs.TaskGroupSummary{ 1812 "web": structs.TaskGroupSummary{}, 1813 }, 1814 Children: new(structs.JobChildrenSummary), 1815 CreateIndex: job.CreateIndex, 1816 ModifyIndex: job.CreateIndex, 1817 } 1818 1819 if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) { 1820 t.Fatalf("exptected: %v, actual: %v", expectedJobSummary, resp2.JobSummary) 1821 } 1822 } 1823 1824 func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) { 1825 t.Parallel() 1826 s1 := testServer(t, nil) 1827 defer s1.Shutdown() 1828 state := s1.fsm.State() 1829 codec := rpcClient(t, s1) 1830 testutil.WaitForLeader(t, s1.RPC) 1831 1832 // Create a job and insert it 1833 job1 := mock.Job() 1834 time.AfterFunc(200*time.Millisecond, func() { 1835 if err := state.UpsertJob(100, job1); err != nil { 1836 t.Fatalf("err: %v", err) 1837 } 1838 }) 1839 1840 // Ensure the job summary request gets fired 1841 req := &structs.JobSummaryRequest{ 1842 JobID: job1.ID, 1843 QueryOptions: structs.QueryOptions{ 1844 Region: "global", 1845 MinQueryIndex: 50, 1846 }, 1847 } 1848 var resp structs.JobSummaryResponse 1849 start := time.Now() 1850 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil { 1851 t.Fatalf("err: %v", err) 1852 } 1853 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1854 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1855 } 1856 1857 // Upsert an allocation for the job which should trigger the watch. 1858 time.AfterFunc(200*time.Millisecond, func() { 1859 alloc := mock.Alloc() 1860 alloc.JobID = job1.ID 1861 alloc.Job = job1 1862 if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil { 1863 t.Fatalf("err: %v", err) 1864 } 1865 }) 1866 req = &structs.JobSummaryRequest{ 1867 JobID: job1.ID, 1868 QueryOptions: structs.QueryOptions{ 1869 Region: "global", 1870 MinQueryIndex: 199, 1871 }, 1872 } 1873 start = time.Now() 1874 var resp1 structs.JobSummaryResponse 1875 start = time.Now() 1876 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil { 1877 t.Fatalf("err: %v", err) 1878 } 1879 1880 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1881 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1882 } 1883 if resp1.Index != 200 { 1884 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1885 } 1886 if resp1.JobSummary == nil { 1887 t.Fatalf("bad: %#v", resp) 1888 } 1889 1890 // Job delete fires watches 1891 time.AfterFunc(100*time.Millisecond, func() { 1892 if err := state.DeleteJob(300, job1.ID); err != nil { 1893 t.Fatalf("err: %v", err) 1894 } 1895 }) 1896 1897 req.QueryOptions.MinQueryIndex = 250 1898 start = time.Now() 1899 1900 var resp2 structs.SingleJobResponse 1901 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil { 1902 t.Fatalf("err: %v", err) 1903 } 1904 1905 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1906 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1907 } 1908 if resp2.Index != 300 { 1909 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 1910 } 1911 if resp2.Job != nil { 1912 t.Fatalf("bad: %#v", resp2.Job) 1913 } 1914 } 1915 1916 func TestJobEndpoint_ListJobs(t *testing.T) { 1917 t.Parallel() 1918 s1 := testServer(t, nil) 1919 defer s1.Shutdown() 1920 codec := rpcClient(t, s1) 1921 testutil.WaitForLeader(t, s1.RPC) 1922 1923 // Create the register request 1924 job := mock.Job() 1925 state := s1.fsm.State() 1926 err := state.UpsertJob(1000, job) 1927 if err != nil { 1928 t.Fatalf("err: %v", err) 1929 } 1930 1931 // Lookup the jobs 1932 get := &structs.JobListRequest{ 1933 QueryOptions: structs.QueryOptions{Region: "global"}, 1934 } 1935 var resp2 structs.JobListResponse 1936 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil { 1937 t.Fatalf("err: %v", err) 1938 } 1939 if resp2.Index != 1000 { 1940 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1941 } 1942 1943 if len(resp2.Jobs) != 1 { 1944 t.Fatalf("bad: %#v", resp2.Jobs) 1945 } 1946 if resp2.Jobs[0].ID != job.ID { 1947 t.Fatalf("bad: %#v", resp2.Jobs[0]) 1948 } 1949 1950 // Lookup the jobs by prefix 1951 get = &structs.JobListRequest{ 1952 QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]}, 1953 } 1954 var resp3 structs.JobListResponse 1955 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil { 1956 t.Fatalf("err: %v", err) 1957 } 1958 if resp3.Index != 1000 { 1959 t.Fatalf("Bad index: %d %d", resp3.Index, 1000) 1960 } 1961 1962 if len(resp3.Jobs) != 1 { 1963 t.Fatalf("bad: %#v", resp3.Jobs) 1964 } 1965 if resp3.Jobs[0].ID != job.ID { 1966 t.Fatalf("bad: %#v", resp3.Jobs[0]) 1967 } 1968 } 1969 1970 func TestJobEndpoint_ListJobs_Blocking(t *testing.T) { 1971 t.Parallel() 1972 s1 := testServer(t, nil) 1973 defer s1.Shutdown() 1974 state := s1.fsm.State() 1975 codec := rpcClient(t, s1) 1976 testutil.WaitForLeader(t, s1.RPC) 1977 1978 // Create the job 1979 job := mock.Job() 1980 1981 // Upsert job triggers watches 1982 time.AfterFunc(100*time.Millisecond, func() { 1983 if err := state.UpsertJob(100, job); err != nil { 1984 t.Fatalf("err: %v", err) 1985 } 1986 }) 1987 1988 req := &structs.JobListRequest{ 1989 QueryOptions: structs.QueryOptions{ 1990 Region: "global", 1991 MinQueryIndex: 50, 1992 }, 1993 } 1994 start := time.Now() 1995 var resp structs.JobListResponse 1996 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil { 1997 t.Fatalf("err: %v", err) 1998 } 1999 2000 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 2001 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2002 } 2003 if resp.Index != 100 { 2004 t.Fatalf("Bad index: %d %d", resp.Index, 100) 2005 } 2006 if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID { 2007 t.Fatalf("bad: %#v", resp) 2008 } 2009 2010 // Job deletion triggers watches 2011 time.AfterFunc(100*time.Millisecond, func() { 2012 if err := state.DeleteJob(200, job.ID); err != nil { 2013 t.Fatalf("err: %v", err) 2014 } 2015 }) 2016 2017 req.MinQueryIndex = 150 2018 start = time.Now() 2019 var resp2 structs.JobListResponse 2020 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil { 2021 t.Fatalf("err: %v", err) 2022 } 2023 2024 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 2025 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 2026 } 2027 if resp2.Index != 200 { 2028 t.Fatalf("Bad index: %d %d", resp2.Index, 200) 2029 } 2030 if len(resp2.Jobs) != 0 { 2031 t.Fatalf("bad: %#v", resp2) 2032 } 2033 } 2034 2035 func TestJobEndpoint_Allocations(t *testing.T) { 2036 t.Parallel() 2037 s1 := testServer(t, nil) 2038 defer s1.Shutdown() 2039 codec := rpcClient(t, s1) 2040 testutil.WaitForLeader(t, s1.RPC) 2041 2042 // Create the register request 2043 alloc1 := mock.Alloc() 2044 alloc2 := mock.Alloc() 2045 alloc2.JobID = alloc1.JobID 2046 state := s1.fsm.State() 2047 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 2048 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 2049 err := state.UpsertAllocs(1000, 2050 []*structs.Allocation{alloc1, alloc2}) 2051 if err != nil { 2052 t.Fatalf("err: %v", err) 2053 } 2054 2055 // Lookup the jobs 2056 get := &structs.JobSpecificRequest{ 2057 JobID: alloc1.JobID, 2058 QueryOptions: structs.QueryOptions{Region: "global"}, 2059 } 2060 var resp2 structs.JobAllocationsResponse 2061 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil { 2062 t.Fatalf("err: %v", err) 2063 } 2064 if resp2.Index != 1000 { 2065 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 2066 } 2067 2068 if len(resp2.Allocations) != 2 { 2069 t.Fatalf("bad: %#v", resp2.Allocations) 2070 } 2071 } 2072 2073 func TestJobEndpoint_Allocations_Blocking(t *testing.T) { 2074 t.Parallel() 2075 s1 := testServer(t, nil) 2076 defer s1.Shutdown() 2077 codec := rpcClient(t, s1) 2078 testutil.WaitForLeader(t, s1.RPC) 2079 2080 // Create the register request 2081 alloc1 := mock.Alloc() 2082 alloc2 := mock.Alloc() 2083 alloc2.JobID = "job1" 2084 state := s1.fsm.State() 2085 2086 // First upsert an unrelated alloc 2087 time.AfterFunc(100*time.Millisecond, func() { 2088 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 2089 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 2090 if err != nil { 2091 t.Fatalf("err: %v", err) 2092 } 2093 }) 2094 2095 // Upsert an alloc for the job we are interested in later 2096 time.AfterFunc(200*time.Millisecond, func() { 2097 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 2098 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 2099 if err != nil { 2100 t.Fatalf("err: %v", err) 2101 } 2102 }) 2103 2104 // Lookup the jobs 2105 get := &structs.JobSpecificRequest{ 2106 JobID: "job1", 2107 QueryOptions: structs.QueryOptions{ 2108 Region: "global", 2109 MinQueryIndex: 150, 2110 }, 2111 } 2112 var resp structs.JobAllocationsResponse 2113 start := time.Now() 2114 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil { 2115 t.Fatalf("err: %v", err) 2116 } 2117 2118 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2119 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2120 } 2121 if resp.Index != 200 { 2122 t.Fatalf("Bad index: %d %d", resp.Index, 200) 2123 } 2124 if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" { 2125 t.Fatalf("bad: %#v", resp.Allocations) 2126 } 2127 } 2128 2129 func TestJobEndpoint_Evaluations(t *testing.T) { 2130 t.Parallel() 2131 s1 := testServer(t, nil) 2132 defer s1.Shutdown() 2133 codec := rpcClient(t, s1) 2134 testutil.WaitForLeader(t, s1.RPC) 2135 2136 // Create the register request 2137 eval1 := mock.Eval() 2138 eval2 := mock.Eval() 2139 eval2.JobID = eval1.JobID 2140 state := s1.fsm.State() 2141 err := state.UpsertEvals(1000, 2142 []*structs.Evaluation{eval1, eval2}) 2143 if err != nil { 2144 t.Fatalf("err: %v", err) 2145 } 2146 2147 // Lookup the jobs 2148 get := &structs.JobSpecificRequest{ 2149 JobID: eval1.JobID, 2150 QueryOptions: structs.QueryOptions{Region: "global"}, 2151 } 2152 var resp2 structs.JobEvaluationsResponse 2153 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil { 2154 t.Fatalf("err: %v", err) 2155 } 2156 if resp2.Index != 1000 { 2157 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 2158 } 2159 2160 if len(resp2.Evaluations) != 2 { 2161 t.Fatalf("bad: %#v", resp2.Evaluations) 2162 } 2163 } 2164 2165 func TestJobEndpoint_Evaluations_Blocking(t *testing.T) { 2166 t.Parallel() 2167 s1 := testServer(t, nil) 2168 defer s1.Shutdown() 2169 codec := rpcClient(t, s1) 2170 testutil.WaitForLeader(t, s1.RPC) 2171 2172 // Create the register request 2173 eval1 := mock.Eval() 2174 eval2 := mock.Eval() 2175 eval2.JobID = "job1" 2176 state := s1.fsm.State() 2177 2178 // First upsert an unrelated eval 2179 time.AfterFunc(100*time.Millisecond, func() { 2180 err := state.UpsertEvals(100, []*structs.Evaluation{eval1}) 2181 if err != nil { 2182 t.Fatalf("err: %v", err) 2183 } 2184 }) 2185 2186 // Upsert an eval for the job we are interested in later 2187 time.AfterFunc(200*time.Millisecond, func() { 2188 err := state.UpsertEvals(200, []*structs.Evaluation{eval2}) 2189 if err != nil { 2190 t.Fatalf("err: %v", err) 2191 } 2192 }) 2193 2194 // Lookup the jobs 2195 get := &structs.JobSpecificRequest{ 2196 JobID: "job1", 2197 QueryOptions: structs.QueryOptions{ 2198 Region: "global", 2199 MinQueryIndex: 150, 2200 }, 2201 } 2202 var resp structs.JobEvaluationsResponse 2203 start := time.Now() 2204 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil { 2205 t.Fatalf("err: %v", err) 2206 } 2207 2208 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2209 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2210 } 2211 if resp.Index != 200 { 2212 t.Fatalf("Bad index: %d %d", resp.Index, 200) 2213 } 2214 if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" { 2215 t.Fatalf("bad: %#v", resp.Evaluations) 2216 } 2217 } 2218 2219 func TestJobEndpoint_Deployments(t *testing.T) { 2220 t.Parallel() 2221 s1 := testServer(t, nil) 2222 defer s1.Shutdown() 2223 codec := rpcClient(t, s1) 2224 testutil.WaitForLeader(t, s1.RPC) 2225 state := s1.fsm.State() 2226 assert := assert.New(t) 2227 2228 // Create the register request 2229 j := mock.Job() 2230 d1 := mock.Deployment() 2231 d2 := mock.Deployment() 2232 d1.JobID = j.ID 2233 d2.JobID = j.ID 2234 assert.Nil(state.UpsertJob(1000, j), "UpsertJob") 2235 assert.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment") 2236 assert.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment") 2237 2238 // Lookup the jobs 2239 get := &structs.JobSpecificRequest{ 2240 JobID: j.ID, 2241 QueryOptions: structs.QueryOptions{Region: "global"}, 2242 } 2243 var resp structs.DeploymentListResponse 2244 assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC") 2245 assert.EqualValues(1002, resp.Index, "response index") 2246 assert.Len(resp.Deployments, 2, "deployments for job") 2247 } 2248 2249 func TestJobEndpoint_Deployments_Blocking(t *testing.T) { 2250 t.Parallel() 2251 s1 := testServer(t, nil) 2252 defer s1.Shutdown() 2253 codec := rpcClient(t, s1) 2254 testutil.WaitForLeader(t, s1.RPC) 2255 state := s1.fsm.State() 2256 assert := assert.New(t) 2257 2258 // Create the register request 2259 j := mock.Job() 2260 d1 := mock.Deployment() 2261 d2 := mock.Deployment() 2262 d2.JobID = j.ID 2263 assert.Nil(state.UpsertJob(50, j), "UpsertJob") 2264 2265 // First upsert an unrelated eval 2266 time.AfterFunc(100*time.Millisecond, func() { 2267 assert.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment") 2268 }) 2269 2270 // Upsert an eval for the job we are interested in later 2271 time.AfterFunc(200*time.Millisecond, func() { 2272 assert.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment") 2273 }) 2274 2275 // Lookup the jobs 2276 get := &structs.JobSpecificRequest{ 2277 JobID: d2.JobID, 2278 QueryOptions: structs.QueryOptions{ 2279 Region: "global", 2280 MinQueryIndex: 150, 2281 }, 2282 } 2283 var resp structs.DeploymentListResponse 2284 start := time.Now() 2285 assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC") 2286 assert.EqualValues(200, resp.Index, "response index") 2287 assert.Len(resp.Deployments, 1, "deployments for job") 2288 assert.Equal(d2.ID, resp.Deployments[0].ID, "returned deployment") 2289 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2290 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2291 } 2292 } 2293 2294 func TestJobEndpoint_LatestDeployment(t *testing.T) { 2295 t.Parallel() 2296 s1 := testServer(t, nil) 2297 defer s1.Shutdown() 2298 codec := rpcClient(t, s1) 2299 testutil.WaitForLeader(t, s1.RPC) 2300 state := s1.fsm.State() 2301 assert := assert.New(t) 2302 2303 // Create the register request 2304 j := mock.Job() 2305 d1 := mock.Deployment() 2306 d2 := mock.Deployment() 2307 d1.JobID = j.ID 2308 d2.JobID = j.ID 2309 d2.CreateIndex = d1.CreateIndex + 100 2310 d2.ModifyIndex = d2.CreateIndex + 100 2311 assert.Nil(state.UpsertJob(1000, j), "UpsertJob") 2312 assert.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment") 2313 assert.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment") 2314 2315 // Lookup the jobs 2316 get := &structs.JobSpecificRequest{ 2317 JobID: j.ID, 2318 QueryOptions: structs.QueryOptions{Region: "global"}, 2319 } 2320 var resp structs.SingleDeploymentResponse 2321 assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC") 2322 assert.EqualValues(1002, resp.Index, "response index") 2323 assert.NotNil(resp.Deployment, "want a deployment") 2324 assert.Equal(d2.ID, resp.Deployment.ID, "latest deployment for job") 2325 } 2326 2327 func TestJobEndpoint_LatestDeployment_Blocking(t *testing.T) { 2328 t.Parallel() 2329 s1 := testServer(t, nil) 2330 defer s1.Shutdown() 2331 codec := rpcClient(t, s1) 2332 testutil.WaitForLeader(t, s1.RPC) 2333 state := s1.fsm.State() 2334 assert := assert.New(t) 2335 2336 // Create the register request 2337 j := mock.Job() 2338 d1 := mock.Deployment() 2339 d2 := mock.Deployment() 2340 d2.JobID = j.ID 2341 assert.Nil(state.UpsertJob(50, j), "UpsertJob") 2342 2343 // First upsert an unrelated eval 2344 time.AfterFunc(100*time.Millisecond, func() { 2345 assert.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment") 2346 }) 2347 2348 // Upsert an eval for the job we are interested in later 2349 time.AfterFunc(200*time.Millisecond, func() { 2350 assert.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment") 2351 }) 2352 2353 // Lookup the jobs 2354 get := &structs.JobSpecificRequest{ 2355 JobID: d2.JobID, 2356 QueryOptions: structs.QueryOptions{ 2357 Region: "global", 2358 MinQueryIndex: 150, 2359 }, 2360 } 2361 var resp structs.SingleDeploymentResponse 2362 start := time.Now() 2363 assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC") 2364 assert.EqualValues(200, resp.Index, "response index") 2365 assert.NotNil(resp.Deployment, "deployment for job") 2366 assert.Equal(d2.ID, resp.Deployment.ID, "returned deployment") 2367 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 2368 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 2369 } 2370 } 2371 2372 func TestJobEndpoint_Plan_WithDiff(t *testing.T) { 2373 t.Parallel() 2374 s1 := testServer(t, func(c *Config) { 2375 c.NumSchedulers = 0 // Prevent automatic dequeue 2376 }) 2377 defer s1.Shutdown() 2378 codec := rpcClient(t, s1) 2379 testutil.WaitForLeader(t, s1.RPC) 2380 2381 // Create the register request 2382 job := mock.Job() 2383 req := &structs.JobRegisterRequest{ 2384 Job: job, 2385 WriteRequest: structs.WriteRequest{Region: "global"}, 2386 } 2387 2388 // Fetch the response 2389 var resp structs.JobRegisterResponse 2390 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 2391 t.Fatalf("err: %v", err) 2392 } 2393 if resp.Index == 0 { 2394 t.Fatalf("bad index: %d", resp.Index) 2395 } 2396 2397 // Create a plan request 2398 planReq := &structs.JobPlanRequest{ 2399 Job: job, 2400 Diff: true, 2401 WriteRequest: structs.WriteRequest{Region: "global"}, 2402 } 2403 2404 // Fetch the response 2405 var planResp structs.JobPlanResponse 2406 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 2407 t.Fatalf("err: %v", err) 2408 } 2409 2410 // Check the response 2411 if planResp.JobModifyIndex == 0 { 2412 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 2413 } 2414 if planResp.Annotations == nil { 2415 t.Fatalf("no annotations") 2416 } 2417 if planResp.Diff == nil { 2418 t.Fatalf("no diff") 2419 } 2420 if len(planResp.FailedTGAllocs) == 0 { 2421 t.Fatalf("no failed task group alloc metrics") 2422 } 2423 } 2424 2425 func TestJobEndpoint_Plan_NoDiff(t *testing.T) { 2426 t.Parallel() 2427 s1 := testServer(t, func(c *Config) { 2428 c.NumSchedulers = 0 // Prevent automatic dequeue 2429 }) 2430 defer s1.Shutdown() 2431 codec := rpcClient(t, s1) 2432 testutil.WaitForLeader(t, s1.RPC) 2433 2434 // Create the register request 2435 job := mock.Job() 2436 req := &structs.JobRegisterRequest{ 2437 Job: job, 2438 WriteRequest: structs.WriteRequest{Region: "global"}, 2439 } 2440 2441 // Fetch the response 2442 var resp structs.JobRegisterResponse 2443 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 2444 t.Fatalf("err: %v", err) 2445 } 2446 if resp.Index == 0 { 2447 t.Fatalf("bad index: %d", resp.Index) 2448 } 2449 2450 // Create a plan request 2451 planReq := &structs.JobPlanRequest{ 2452 Job: job, 2453 Diff: false, 2454 WriteRequest: structs.WriteRequest{Region: "global"}, 2455 } 2456 2457 // Fetch the response 2458 var planResp structs.JobPlanResponse 2459 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 2460 t.Fatalf("err: %v", err) 2461 } 2462 2463 // Check the response 2464 if planResp.JobModifyIndex == 0 { 2465 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 2466 } 2467 if planResp.Annotations == nil { 2468 t.Fatalf("no annotations") 2469 } 2470 if planResp.Diff != nil { 2471 t.Fatalf("got diff") 2472 } 2473 if len(planResp.FailedTGAllocs) == 0 { 2474 t.Fatalf("no failed task group alloc metrics") 2475 } 2476 } 2477 2478 func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) { 2479 t.Parallel() 2480 s1 := testServer(t, func(c *Config) { 2481 c.NumSchedulers = 0 // Prevent automatic dequeue 2482 }) 2483 defer s1.Shutdown() 2484 codec := rpcClient(t, s1) 2485 testutil.WaitForLeader(t, s1.RPC) 2486 2487 // Enable vault 2488 tr, f := true, false 2489 s1.config.VaultConfig.Enabled = &tr 2490 s1.config.VaultConfig.AllowUnauthenticated = &f 2491 2492 // Replace the Vault Client on the server 2493 tvc := &TestVaultClient{} 2494 s1.vault = tvc 2495 2496 policy := "foo" 2497 goodToken := structs.GenerateUUID() 2498 goodPolicies := []string{"foo", "bar", "baz"} 2499 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 2500 2501 // Create the register request with a job asking for a vault policy 2502 job := mock.Job() 2503 job.VaultToken = goodToken 2504 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 2505 Policies: []string{policy}, 2506 ChangeMode: structs.VaultChangeModeRestart, 2507 } 2508 req := &structs.JobRegisterRequest{ 2509 Job: job, 2510 WriteRequest: structs.WriteRequest{Region: "global"}, 2511 } 2512 2513 // Fetch the response 2514 var resp structs.JobRegisterResponse 2515 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 2516 t.Fatalf("bad: %v", err) 2517 } 2518 2519 // Check for the job in the FSM 2520 state := s1.fsm.State() 2521 ws := memdb.NewWatchSet() 2522 out, err := state.JobByID(ws, job.ID) 2523 if err != nil { 2524 t.Fatalf("err: %v", err) 2525 } 2526 if out == nil { 2527 t.Fatalf("expected job") 2528 } 2529 if out.CreateIndex != resp.JobModifyIndex { 2530 t.Fatalf("index mis-match") 2531 } 2532 2533 // Check that there is an implicit vault constraint 2534 constraints := out.TaskGroups[0].Constraints 2535 if len(constraints) != 1 { 2536 t.Fatalf("Expected an implicit constraint") 2537 } 2538 2539 if !constraints[0].Equal(vaultConstraint) { 2540 t.Fatalf("Expected implicit vault constraint") 2541 } 2542 } 2543 2544 func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) { 2545 t.Parallel() 2546 s1 := testServer(t, func(c *Config) { 2547 c.NumSchedulers = 0 // Prevent automatic dequeue 2548 }) 2549 defer s1.Shutdown() 2550 codec := rpcClient(t, s1) 2551 testutil.WaitForLeader(t, s1.RPC) 2552 2553 // Create the register request with a job asking for a template that sends a 2554 // signal 2555 job := mock.Job() 2556 signal := "SIGUSR1" 2557 job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{ 2558 &structs.Template{ 2559 SourcePath: "foo", 2560 DestPath: "bar", 2561 ChangeMode: structs.TemplateChangeModeSignal, 2562 ChangeSignal: signal, 2563 }, 2564 } 2565 req := &structs.JobRegisterRequest{ 2566 Job: job, 2567 WriteRequest: structs.WriteRequest{Region: "global"}, 2568 } 2569 2570 // Fetch the response 2571 var resp structs.JobRegisterResponse 2572 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 2573 t.Fatalf("bad: %v", err) 2574 } 2575 2576 // Check for the job in the FSM 2577 state := s1.fsm.State() 2578 ws := memdb.NewWatchSet() 2579 out, err := state.JobByID(ws, job.ID) 2580 if err != nil { 2581 t.Fatalf("err: %v", err) 2582 } 2583 if out == nil { 2584 t.Fatalf("expected job") 2585 } 2586 if out.CreateIndex != resp.JobModifyIndex { 2587 t.Fatalf("index mis-match") 2588 } 2589 2590 // Check that there is an implicit signal constraint 2591 constraints := out.TaskGroups[0].Constraints 2592 if len(constraints) != 1 { 2593 t.Fatalf("Expected an implicit constraint") 2594 } 2595 2596 sigConstraint := getSignalConstraint([]string{signal}) 2597 2598 if !constraints[0].Equal(sigConstraint) { 2599 t.Fatalf("Expected implicit vault constraint") 2600 } 2601 } 2602 2603 func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) { 2604 t.Parallel() 2605 // Create a mock job with an invalid config 2606 job := mock.Job() 2607 job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{ 2608 "foo": "bar", 2609 } 2610 2611 err, warnings := validateJob(job) 2612 if err == nil || !strings.Contains(err.Error(), "-> config") { 2613 t.Fatalf("Expected config error; got %v", err) 2614 } 2615 2616 if warnings != nil { 2617 t.Fatalf("got unexpected warnings: %v", warnings) 2618 } 2619 } 2620 2621 func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) { 2622 t.Parallel() 2623 // Create a mock job that wants to send a signal to a driver that can't 2624 job := mock.Job() 2625 job.TaskGroups[0].Tasks[0].Driver = "qemu" 2626 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 2627 Policies: []string{"foo"}, 2628 ChangeMode: structs.VaultChangeModeSignal, 2629 ChangeSignal: "SIGUSR1", 2630 } 2631 2632 err, warnings := validateJob(job) 2633 if err == nil || !strings.Contains(err.Error(), "support sending signals") { 2634 t.Fatalf("Expected signal feasibility error; got %v", err) 2635 } 2636 2637 if warnings != nil { 2638 t.Fatalf("got unexpected warnings: %v", warnings) 2639 } 2640 } 2641 2642 func TestJobEndpoint_ValidateJobUpdate(t *testing.T) { 2643 t.Parallel() 2644 old := mock.Job() 2645 new := mock.Job() 2646 2647 if err := validateJobUpdate(old, new); err != nil { 2648 t.Errorf("expected update to be valid but got: %v", err) 2649 } 2650 2651 new.Type = "batch" 2652 if err := validateJobUpdate(old, new); err == nil { 2653 t.Errorf("expected err when setting new job to a different type") 2654 } else { 2655 t.Log(err) 2656 } 2657 2658 new = mock.Job() 2659 new.Periodic = &structs.PeriodicConfig{Enabled: true} 2660 if err := validateJobUpdate(old, new); err == nil { 2661 t.Errorf("expected err when setting new job to periodic") 2662 } else { 2663 t.Log(err) 2664 } 2665 2666 new = mock.Job() 2667 new.ParameterizedJob = &structs.ParameterizedJobConfig{} 2668 if err := validateJobUpdate(old, new); err == nil { 2669 t.Errorf("expected err when setting new job to parameterized") 2670 } else { 2671 t.Log(err) 2672 } 2673 } 2674 2675 func TestJobEndpoint_Dispatch(t *testing.T) { 2676 t.Parallel() 2677 2678 // No requirements 2679 d1 := mock.Job() 2680 d1.Type = structs.JobTypeBatch 2681 d1.ParameterizedJob = &structs.ParameterizedJobConfig{} 2682 2683 // Require input data 2684 d2 := mock.Job() 2685 d2.Type = structs.JobTypeBatch 2686 d2.ParameterizedJob = &structs.ParameterizedJobConfig{ 2687 Payload: structs.DispatchPayloadRequired, 2688 } 2689 2690 // Disallow input data 2691 d3 := mock.Job() 2692 d3.Type = structs.JobTypeBatch 2693 d3.ParameterizedJob = &structs.ParameterizedJobConfig{ 2694 Payload: structs.DispatchPayloadForbidden, 2695 } 2696 2697 // Require meta 2698 d4 := mock.Job() 2699 d4.Type = structs.JobTypeBatch 2700 d4.ParameterizedJob = &structs.ParameterizedJobConfig{ 2701 MetaRequired: []string{"foo", "bar"}, 2702 } 2703 2704 // Optional meta 2705 d5 := mock.Job() 2706 d5.Type = structs.JobTypeBatch 2707 d5.ParameterizedJob = &structs.ParameterizedJobConfig{ 2708 MetaOptional: []string{"foo", "bar"}, 2709 } 2710 2711 // Periodic dispatch job 2712 d6 := mock.PeriodicJob() 2713 d6.ParameterizedJob = &structs.ParameterizedJobConfig{} 2714 2715 d7 := mock.Job() 2716 d7.Type = structs.JobTypeBatch 2717 d7.ParameterizedJob = &structs.ParameterizedJobConfig{} 2718 d7.Stop = true 2719 2720 reqNoInputNoMeta := &structs.JobDispatchRequest{} 2721 reqInputDataNoMeta := &structs.JobDispatchRequest{ 2722 Payload: []byte("hello world"), 2723 } 2724 reqNoInputDataMeta := &structs.JobDispatchRequest{ 2725 Meta: map[string]string{ 2726 "foo": "f1", 2727 "bar": "f2", 2728 }, 2729 } 2730 reqInputDataMeta := &structs.JobDispatchRequest{ 2731 Payload: []byte("hello world"), 2732 Meta: map[string]string{ 2733 "foo": "f1", 2734 "bar": "f2", 2735 }, 2736 } 2737 reqBadMeta := &structs.JobDispatchRequest{ 2738 Payload: []byte("hello world"), 2739 Meta: map[string]string{ 2740 "foo": "f1", 2741 "bar": "f2", 2742 "baz": "f3", 2743 }, 2744 } 2745 reqInputDataTooLarge := &structs.JobDispatchRequest{ 2746 Payload: make([]byte, DispatchPayloadSizeLimit+100), 2747 } 2748 2749 type testCase struct { 2750 name string 2751 parameterizedJob *structs.Job 2752 dispatchReq *structs.JobDispatchRequest 2753 noEval bool 2754 err bool 2755 errStr string 2756 } 2757 cases := []testCase{ 2758 { 2759 name: "optional input data w/ data", 2760 parameterizedJob: d1, 2761 dispatchReq: reqInputDataNoMeta, 2762 err: false, 2763 }, 2764 { 2765 name: "optional input data w/o data", 2766 parameterizedJob: d1, 2767 dispatchReq: reqNoInputNoMeta, 2768 err: false, 2769 }, 2770 { 2771 name: "require input data w/ data", 2772 parameterizedJob: d2, 2773 dispatchReq: reqInputDataNoMeta, 2774 err: false, 2775 }, 2776 { 2777 name: "require input data w/o data", 2778 parameterizedJob: d2, 2779 dispatchReq: reqNoInputNoMeta, 2780 err: true, 2781 errStr: "not provided but required", 2782 }, 2783 { 2784 name: "disallow input data w/o data", 2785 parameterizedJob: d3, 2786 dispatchReq: reqNoInputNoMeta, 2787 err: false, 2788 }, 2789 { 2790 name: "disallow input data w/ data", 2791 parameterizedJob: d3, 2792 dispatchReq: reqInputDataNoMeta, 2793 err: true, 2794 errStr: "provided but forbidden", 2795 }, 2796 { 2797 name: "require meta w/ meta", 2798 parameterizedJob: d4, 2799 dispatchReq: reqInputDataMeta, 2800 err: false, 2801 }, 2802 { 2803 name: "require meta w/o meta", 2804 parameterizedJob: d4, 2805 dispatchReq: reqNoInputNoMeta, 2806 err: true, 2807 errStr: "did not provide required meta keys", 2808 }, 2809 { 2810 name: "optional meta w/ meta", 2811 parameterizedJob: d5, 2812 dispatchReq: reqNoInputDataMeta, 2813 err: false, 2814 }, 2815 { 2816 name: "optional meta w/o meta", 2817 parameterizedJob: d5, 2818 dispatchReq: reqNoInputNoMeta, 2819 err: false, 2820 }, 2821 { 2822 name: "optional meta w/ bad meta", 2823 parameterizedJob: d5, 2824 dispatchReq: reqBadMeta, 2825 err: true, 2826 errStr: "unpermitted metadata keys", 2827 }, 2828 { 2829 name: "optional input w/ too big of input", 2830 parameterizedJob: d1, 2831 dispatchReq: reqInputDataTooLarge, 2832 err: true, 2833 errStr: "Payload exceeds maximum size", 2834 }, 2835 { 2836 name: "periodic job dispatched, ensure no eval", 2837 parameterizedJob: d6, 2838 dispatchReq: reqNoInputNoMeta, 2839 noEval: true, 2840 }, 2841 { 2842 name: "periodic job stopped, ensure error", 2843 parameterizedJob: d7, 2844 dispatchReq: reqNoInputNoMeta, 2845 err: true, 2846 errStr: "stopped", 2847 }, 2848 } 2849 2850 for _, tc := range cases { 2851 t.Run(tc.name, func(t *testing.T) { 2852 s1 := testServer(t, func(c *Config) { 2853 c.NumSchedulers = 0 // Prevent automatic dequeue 2854 }) 2855 defer s1.Shutdown() 2856 codec := rpcClient(t, s1) 2857 testutil.WaitForLeader(t, s1.RPC) 2858 2859 // Create the register request 2860 regReq := &structs.JobRegisterRequest{ 2861 Job: tc.parameterizedJob, 2862 WriteRequest: structs.WriteRequest{Region: "global"}, 2863 } 2864 2865 // Fetch the response 2866 var regResp structs.JobRegisterResponse 2867 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", regReq, ®Resp); err != nil { 2868 t.Fatalf("err: %v", err) 2869 } 2870 2871 // Now try to dispatch 2872 tc.dispatchReq.JobID = tc.parameterizedJob.ID 2873 tc.dispatchReq.WriteRequest = structs.WriteRequest{Region: "global"} 2874 2875 var dispatchResp structs.JobDispatchResponse 2876 dispatchErr := msgpackrpc.CallWithCodec(codec, "Job.Dispatch", tc.dispatchReq, &dispatchResp) 2877 2878 if dispatchErr == nil { 2879 if tc.err { 2880 t.Fatalf("Expected error: %v", dispatchErr) 2881 } 2882 2883 // Check that we got an eval and job id back 2884 switch dispatchResp.EvalID { 2885 case "": 2886 if !tc.noEval { 2887 t.Fatalf("Bad response") 2888 } 2889 default: 2890 if tc.noEval { 2891 t.Fatalf("Got eval %q", dispatchResp.EvalID) 2892 } 2893 } 2894 2895 if dispatchResp.DispatchedJobID == "" { 2896 t.Fatalf("Bad response") 2897 } 2898 2899 state := s1.fsm.State() 2900 ws := memdb.NewWatchSet() 2901 out, err := state.JobByID(ws, dispatchResp.DispatchedJobID) 2902 if err != nil { 2903 t.Fatalf("err: %v", err) 2904 } 2905 if out == nil { 2906 t.Fatalf("expected job") 2907 } 2908 if out.CreateIndex != dispatchResp.JobCreateIndex { 2909 t.Fatalf("index mis-match") 2910 } 2911 if out.ParentID != tc.parameterizedJob.ID { 2912 t.Fatalf("bad parent ID") 2913 } 2914 2915 if tc.noEval { 2916 return 2917 } 2918 2919 // Lookup the evaluation 2920 eval, err := state.EvalByID(ws, dispatchResp.EvalID) 2921 if err != nil { 2922 t.Fatalf("err: %v", err) 2923 } 2924 2925 if eval == nil { 2926 t.Fatalf("expected eval") 2927 } 2928 if eval.CreateIndex != dispatchResp.EvalCreateIndex { 2929 t.Fatalf("index mis-match") 2930 } 2931 } else { 2932 if !tc.err { 2933 t.Fatalf("Got unexpected error: %v", dispatchErr) 2934 } else if !strings.Contains(dispatchErr.Error(), tc.errStr) { 2935 t.Fatalf("Expected err to include %q; got %v", tc.errStr, dispatchErr) 2936 } 2937 } 2938 }) 2939 } 2940 }