github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/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/nomad/mock" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "github.com/hashicorp/nomad/testutil" 15 ) 16 17 func TestJobEndpoint_Register(t *testing.T) { 18 s1 := testServer(t, func(c *Config) { 19 c.NumSchedulers = 0 // Prevent automatic dequeue 20 }) 21 defer s1.Shutdown() 22 codec := rpcClient(t, s1) 23 testutil.WaitForLeader(t, s1.RPC) 24 25 // Create the register request 26 job := mock.Job() 27 req := &structs.JobRegisterRequest{ 28 Job: job, 29 WriteRequest: structs.WriteRequest{Region: "global"}, 30 } 31 32 // Fetch the response 33 var resp structs.JobRegisterResponse 34 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 if resp.Index == 0 { 38 t.Fatalf("bad index: %d", resp.Index) 39 } 40 41 // Check for the node in the FSM 42 state := s1.fsm.State() 43 ws := memdb.NewWatchSet() 44 out, err := state.JobByID(ws, job.ID) 45 if err != nil { 46 t.Fatalf("err: %v", err) 47 } 48 if out == nil { 49 t.Fatalf("expected job") 50 } 51 if out.CreateIndex != resp.JobModifyIndex { 52 t.Fatalf("index mis-match") 53 } 54 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 55 expectedServiceName := "web-frontend" 56 if serviceName != expectedServiceName { 57 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 58 } 59 60 // Lookup the evaluation 61 eval, err := state.EvalByID(ws, resp.EvalID) 62 if err != nil { 63 t.Fatalf("err: %v", err) 64 } 65 if eval == nil { 66 t.Fatalf("expected eval") 67 } 68 if eval.CreateIndex != resp.EvalCreateIndex { 69 t.Fatalf("index mis-match") 70 } 71 72 if eval.Priority != job.Priority { 73 t.Fatalf("bad: %#v", eval) 74 } 75 if eval.Type != job.Type { 76 t.Fatalf("bad: %#v", eval) 77 } 78 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 79 t.Fatalf("bad: %#v", eval) 80 } 81 if eval.JobID != job.ID { 82 t.Fatalf("bad: %#v", eval) 83 } 84 if eval.JobModifyIndex != resp.JobModifyIndex { 85 t.Fatalf("bad: %#v", eval) 86 } 87 if eval.Status != structs.EvalStatusPending { 88 t.Fatalf("bad: %#v", eval) 89 } 90 } 91 92 func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) { 93 s1 := testServer(t, func(c *Config) { 94 c.NumSchedulers = 0 // Prevent automatic dequeue 95 }) 96 defer s1.Shutdown() 97 codec := rpcClient(t, s1) 98 testutil.WaitForLeader(t, s1.RPC) 99 100 // Create the register request with a job containing an invalid driver 101 // config 102 job := mock.Job() 103 job.TaskGroups[0].Tasks[0].Config["foo"] = 1 104 req := &structs.JobRegisterRequest{ 105 Job: job, 106 WriteRequest: structs.WriteRequest{Region: "global"}, 107 } 108 109 // Fetch the response 110 var resp structs.JobRegisterResponse 111 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 112 if err == nil { 113 t.Fatalf("expected a validation error") 114 } 115 116 if !strings.Contains(err.Error(), "-> config:") { 117 t.Fatalf("expected a driver config validation error but got: %v", err) 118 } 119 } 120 121 func TestJobEndpoint_Register_Payload(t *testing.T) { 122 s1 := testServer(t, func(c *Config) { 123 c.NumSchedulers = 0 // Prevent automatic dequeue 124 }) 125 defer s1.Shutdown() 126 codec := rpcClient(t, s1) 127 testutil.WaitForLeader(t, s1.RPC) 128 129 // Create the register request with a job containing an invalid driver 130 // config 131 job := mock.Job() 132 job.Payload = []byte{0x1} 133 req := &structs.JobRegisterRequest{ 134 Job: job, 135 WriteRequest: structs.WriteRequest{Region: "global"}, 136 } 137 138 // Fetch the response 139 var resp structs.JobRegisterResponse 140 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 141 if err == nil { 142 t.Fatalf("expected a validation error") 143 } 144 145 if !strings.Contains(err.Error(), "payload") { 146 t.Fatalf("expected a payload error but got: %v", err) 147 } 148 } 149 150 func TestJobEndpoint_Register_Existing(t *testing.T) { 151 s1 := testServer(t, func(c *Config) { 152 c.NumSchedulers = 0 // Prevent automatic dequeue 153 }) 154 defer s1.Shutdown() 155 codec := rpcClient(t, s1) 156 testutil.WaitForLeader(t, s1.RPC) 157 158 // Create the register request 159 job := mock.Job() 160 req := &structs.JobRegisterRequest{ 161 Job: job, 162 WriteRequest: structs.WriteRequest{Region: "global"}, 163 } 164 165 // Fetch the response 166 var resp structs.JobRegisterResponse 167 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 168 t.Fatalf("err: %v", err) 169 } 170 if resp.Index == 0 { 171 t.Fatalf("bad index: %d", resp.Index) 172 } 173 174 // Update the job definition 175 job2 := mock.Job() 176 job2.Priority = 100 177 job2.ID = job.ID 178 req.Job = job2 179 180 // Attempt update 181 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 182 t.Fatalf("err: %v", err) 183 } 184 if resp.Index == 0 { 185 t.Fatalf("bad index: %d", resp.Index) 186 } 187 188 // Check for the node in the FSM 189 state := s1.fsm.State() 190 ws := memdb.NewWatchSet() 191 out, err := state.JobByID(ws, job.ID) 192 if err != nil { 193 t.Fatalf("err: %v", err) 194 } 195 if out == nil { 196 t.Fatalf("expected job") 197 } 198 if out.ModifyIndex != resp.JobModifyIndex { 199 t.Fatalf("index mis-match") 200 } 201 if out.Priority != 100 { 202 t.Fatalf("expected update") 203 } 204 205 // Lookup the evaluation 206 eval, err := state.EvalByID(ws, resp.EvalID) 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 if eval == nil { 211 t.Fatalf("expected eval") 212 } 213 if eval.CreateIndex != resp.EvalCreateIndex { 214 t.Fatalf("index mis-match") 215 } 216 217 if eval.Priority != job2.Priority { 218 t.Fatalf("bad: %#v", eval) 219 } 220 if eval.Type != job2.Type { 221 t.Fatalf("bad: %#v", eval) 222 } 223 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 224 t.Fatalf("bad: %#v", eval) 225 } 226 if eval.JobID != job2.ID { 227 t.Fatalf("bad: %#v", eval) 228 } 229 if eval.JobModifyIndex != resp.JobModifyIndex { 230 t.Fatalf("bad: %#v", eval) 231 } 232 if eval.Status != structs.EvalStatusPending { 233 t.Fatalf("bad: %#v", eval) 234 } 235 } 236 237 func TestJobEndpoint_Register_Periodic(t *testing.T) { 238 s1 := testServer(t, func(c *Config) { 239 c.NumSchedulers = 0 // Prevent automatic dequeue 240 }) 241 defer s1.Shutdown() 242 codec := rpcClient(t, s1) 243 testutil.WaitForLeader(t, s1.RPC) 244 245 // Create the register request for a periodic job. 246 job := mock.PeriodicJob() 247 req := &structs.JobRegisterRequest{ 248 Job: job, 249 WriteRequest: structs.WriteRequest{Region: "global"}, 250 } 251 252 // Fetch the response 253 var resp structs.JobRegisterResponse 254 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 255 t.Fatalf("err: %v", err) 256 } 257 if resp.JobModifyIndex == 0 { 258 t.Fatalf("bad index: %d", resp.Index) 259 } 260 261 // Check for the node in the FSM 262 state := s1.fsm.State() 263 ws := memdb.NewWatchSet() 264 out, err := state.JobByID(ws, job.ID) 265 if err != nil { 266 t.Fatalf("err: %v", err) 267 } 268 if out == nil { 269 t.Fatalf("expected job") 270 } 271 if out.CreateIndex != resp.JobModifyIndex { 272 t.Fatalf("index mis-match") 273 } 274 serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name 275 expectedServiceName := "web-frontend" 276 if serviceName != expectedServiceName { 277 t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName) 278 } 279 280 if resp.EvalID != "" { 281 t.Fatalf("Register created an eval for a periodic job") 282 } 283 } 284 285 func TestJobEndpoint_Register_ParameterizedJob(t *testing.T) { 286 s1 := testServer(t, func(c *Config) { 287 c.NumSchedulers = 0 // Prevent automatic dequeue 288 }) 289 defer s1.Shutdown() 290 codec := rpcClient(t, s1) 291 testutil.WaitForLeader(t, s1.RPC) 292 293 // Create the register request for a parameterized job. 294 job := mock.Job() 295 job.Type = structs.JobTypeBatch 296 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 297 req := &structs.JobRegisterRequest{ 298 Job: job, 299 WriteRequest: structs.WriteRequest{Region: "global"}, 300 } 301 302 // Fetch the response 303 var resp structs.JobRegisterResponse 304 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 305 t.Fatalf("err: %v", err) 306 } 307 if resp.JobModifyIndex == 0 { 308 t.Fatalf("bad index: %d", resp.Index) 309 } 310 311 // Check for the job in the FSM 312 state := s1.fsm.State() 313 ws := memdb.NewWatchSet() 314 out, err := state.JobByID(ws, job.ID) 315 if err != nil { 316 t.Fatalf("err: %v", err) 317 } 318 if out == nil { 319 t.Fatalf("expected job") 320 } 321 if out.CreateIndex != resp.JobModifyIndex { 322 t.Fatalf("index mis-match") 323 } 324 if resp.EvalID != "" { 325 t.Fatalf("Register created an eval for a parameterized job") 326 } 327 } 328 329 func TestJobEndpoint_Register_EnforceIndex(t *testing.T) { 330 s1 := testServer(t, func(c *Config) { 331 c.NumSchedulers = 0 // Prevent automatic dequeue 332 }) 333 defer s1.Shutdown() 334 codec := rpcClient(t, s1) 335 testutil.WaitForLeader(t, s1.RPC) 336 337 // Create the register request and enforcing an incorrect index 338 job := mock.Job() 339 req := &structs.JobRegisterRequest{ 340 Job: job, 341 EnforceIndex: true, 342 JobModifyIndex: 100, // Not registered yet so not possible 343 WriteRequest: structs.WriteRequest{Region: "global"}, 344 } 345 346 // Fetch the response 347 var resp structs.JobRegisterResponse 348 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 349 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 350 t.Fatalf("expected enforcement error") 351 } 352 353 // Create the register request and enforcing it is new 354 req = &structs.JobRegisterRequest{ 355 Job: job, 356 EnforceIndex: true, 357 JobModifyIndex: 0, 358 WriteRequest: structs.WriteRequest{Region: "global"}, 359 } 360 361 // Fetch the response 362 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 363 t.Fatalf("err: %v", err) 364 } 365 if resp.Index == 0 { 366 t.Fatalf("bad index: %d", resp.Index) 367 } 368 369 curIndex := resp.JobModifyIndex 370 371 // Check for the node in the FSM 372 state := s1.fsm.State() 373 ws := memdb.NewWatchSet() 374 out, err := state.JobByID(ws, job.ID) 375 if err != nil { 376 t.Fatalf("err: %v", err) 377 } 378 if out == nil { 379 t.Fatalf("expected job") 380 } 381 if out.CreateIndex != resp.JobModifyIndex { 382 t.Fatalf("index mis-match") 383 } 384 385 // Reregister request and enforcing it be a new job 386 req = &structs.JobRegisterRequest{ 387 Job: job, 388 EnforceIndex: true, 389 JobModifyIndex: 0, 390 WriteRequest: structs.WriteRequest{Region: "global"}, 391 } 392 393 // Fetch the response 394 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 395 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 396 t.Fatalf("expected enforcement error") 397 } 398 399 // Reregister request and enforcing it be at an incorrect index 400 req = &structs.JobRegisterRequest{ 401 Job: job, 402 EnforceIndex: true, 403 JobModifyIndex: curIndex - 1, 404 WriteRequest: structs.WriteRequest{Region: "global"}, 405 } 406 407 // Fetch the response 408 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 409 if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { 410 t.Fatalf("expected enforcement error") 411 } 412 413 // Reregister request and enforcing it be at the correct index 414 job.Priority = job.Priority + 1 415 req = &structs.JobRegisterRequest{ 416 Job: job, 417 EnforceIndex: true, 418 JobModifyIndex: curIndex, 419 WriteRequest: structs.WriteRequest{Region: "global"}, 420 } 421 422 // Fetch the response 423 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 424 t.Fatalf("err: %v", err) 425 } 426 if resp.Index == 0 { 427 t.Fatalf("bad index: %d", resp.Index) 428 } 429 430 out, err = state.JobByID(ws, job.ID) 431 if err != nil { 432 t.Fatalf("err: %v", err) 433 } 434 if out == nil { 435 t.Fatalf("expected job") 436 } 437 if out.Priority != job.Priority { 438 t.Fatalf("priority mis-match") 439 } 440 } 441 442 func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) { 443 s1 := testServer(t, func(c *Config) { 444 c.NumSchedulers = 0 // Prevent automatic dequeue 445 f := false 446 c.VaultConfig.Enabled = &f 447 }) 448 defer s1.Shutdown() 449 codec := rpcClient(t, s1) 450 testutil.WaitForLeader(t, s1.RPC) 451 452 // Create the register request with a job asking for a vault policy 453 job := mock.Job() 454 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 455 Policies: []string{"foo"}, 456 ChangeMode: structs.VaultChangeModeRestart, 457 } 458 req := &structs.JobRegisterRequest{ 459 Job: job, 460 WriteRequest: structs.WriteRequest{Region: "global"}, 461 } 462 463 // Fetch the response 464 var resp structs.JobRegisterResponse 465 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 466 if err == nil || !strings.Contains(err.Error(), "Vault not enabled") { 467 t.Fatalf("expected Vault not enabled error: %v", err) 468 } 469 } 470 471 func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) { 472 s1 := testServer(t, func(c *Config) { 473 c.NumSchedulers = 0 // Prevent automatic dequeue 474 }) 475 defer s1.Shutdown() 476 codec := rpcClient(t, s1) 477 testutil.WaitForLeader(t, s1.RPC) 478 479 // Enable vault and allow authenticated 480 tr := true 481 s1.config.VaultConfig.Enabled = &tr 482 s1.config.VaultConfig.AllowUnauthenticated = &tr 483 484 // Replace the Vault Client on the server 485 s1.vault = &TestVaultClient{} 486 487 // Create the register request with a job asking for a vault policy 488 job := mock.Job() 489 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 490 Policies: []string{"foo"}, 491 ChangeMode: structs.VaultChangeModeRestart, 492 } 493 req := &structs.JobRegisterRequest{ 494 Job: job, 495 WriteRequest: structs.WriteRequest{Region: "global"}, 496 } 497 498 // Fetch the response 499 var resp structs.JobRegisterResponse 500 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 501 if err != nil { 502 t.Fatalf("bad: %v", err) 503 } 504 505 // Check for the job in the FSM 506 state := s1.fsm.State() 507 ws := memdb.NewWatchSet() 508 out, err := state.JobByID(ws, job.ID) 509 if err != nil { 510 t.Fatalf("err: %v", err) 511 } 512 if out == nil { 513 t.Fatalf("expected job") 514 } 515 if out.CreateIndex != resp.JobModifyIndex { 516 t.Fatalf("index mis-match") 517 } 518 } 519 520 func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) { 521 s1 := testServer(t, func(c *Config) { 522 c.NumSchedulers = 0 // Prevent automatic dequeue 523 }) 524 defer s1.Shutdown() 525 codec := rpcClient(t, s1) 526 testutil.WaitForLeader(t, s1.RPC) 527 528 // Enable vault 529 tr, f := true, false 530 s1.config.VaultConfig.Enabled = &tr 531 s1.config.VaultConfig.AllowUnauthenticated = &f 532 533 // Replace the Vault Client on the server 534 s1.vault = &TestVaultClient{} 535 536 // Create the register request with a job asking for a vault policy but 537 // don't send a Vault token 538 job := mock.Job() 539 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 540 Policies: []string{"foo"}, 541 ChangeMode: structs.VaultChangeModeRestart, 542 } 543 req := &structs.JobRegisterRequest{ 544 Job: job, 545 WriteRequest: structs.WriteRequest{Region: "global"}, 546 } 547 548 // Fetch the response 549 var resp structs.JobRegisterResponse 550 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 551 if err == nil || !strings.Contains(err.Error(), "missing Vault Token") { 552 t.Fatalf("expected Vault not enabled error: %v", err) 553 } 554 } 555 556 func TestJobEndpoint_Register_Vault_Policies(t *testing.T) { 557 s1 := testServer(t, func(c *Config) { 558 c.NumSchedulers = 0 // Prevent automatic dequeue 559 }) 560 defer s1.Shutdown() 561 codec := rpcClient(t, s1) 562 testutil.WaitForLeader(t, s1.RPC) 563 564 // Enable vault 565 tr, f := true, false 566 s1.config.VaultConfig.Enabled = &tr 567 s1.config.VaultConfig.AllowUnauthenticated = &f 568 569 // Replace the Vault Client on the server 570 tvc := &TestVaultClient{} 571 s1.vault = tvc 572 573 // Add three tokens: one that allows the requesting policy, one that does 574 // not and one that returns an error 575 policy := "foo" 576 577 badToken := structs.GenerateUUID() 578 badPolicies := []string{"a", "b", "c"} 579 tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies) 580 581 goodToken := structs.GenerateUUID() 582 goodPolicies := []string{"foo", "bar", "baz"} 583 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 584 585 rootToken := structs.GenerateUUID() 586 rootPolicies := []string{"root"} 587 tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies) 588 589 errToken := structs.GenerateUUID() 590 expectedErr := fmt.Errorf("return errors from vault") 591 tvc.SetLookupTokenError(errToken, expectedErr) 592 593 // Create the register request with a job asking for a vault policy but 594 // send the bad Vault token 595 job := mock.Job() 596 job.VaultToken = badToken 597 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 598 Policies: []string{policy}, 599 ChangeMode: structs.VaultChangeModeRestart, 600 } 601 req := &structs.JobRegisterRequest{ 602 Job: job, 603 WriteRequest: structs.WriteRequest{Region: "global"}, 604 } 605 606 // Fetch the response 607 var resp structs.JobRegisterResponse 608 err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 609 if err == nil || !strings.Contains(err.Error(), 610 "doesn't allow access to the following policies: "+policy) { 611 t.Fatalf("expected permission denied error: %v", err) 612 } 613 614 // Use the err token 615 job.VaultToken = errToken 616 err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp) 617 if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) { 618 t.Fatalf("expected permission denied error: %v", err) 619 } 620 621 // Use the good token 622 job.VaultToken = goodToken 623 624 // Fetch the response 625 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 626 t.Fatalf("bad: %v", err) 627 } 628 629 // Check for the job in the FSM 630 state := s1.fsm.State() 631 ws := memdb.NewWatchSet() 632 out, err := state.JobByID(ws, job.ID) 633 if err != nil { 634 t.Fatalf("err: %v", err) 635 } 636 if out == nil { 637 t.Fatalf("expected job") 638 } 639 if out.CreateIndex != resp.JobModifyIndex { 640 t.Fatalf("index mis-match") 641 } 642 if out.VaultToken != "" { 643 t.Fatalf("vault token not cleared") 644 } 645 646 // Check that an implicit constraint was created 647 constraints := out.TaskGroups[0].Constraints 648 if l := len(constraints); l != 1 { 649 t.Fatalf("Unexpected number of tests: %v", l) 650 } 651 652 if !constraints[0].Equal(vaultConstraint) { 653 t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint) 654 } 655 656 // Create the register request with another job asking for a vault policy but 657 // send the root Vault token 658 job2 := mock.Job() 659 job2.VaultToken = rootToken 660 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 661 Policies: []string{policy}, 662 ChangeMode: structs.VaultChangeModeRestart, 663 } 664 req = &structs.JobRegisterRequest{ 665 Job: job2, 666 WriteRequest: structs.WriteRequest{Region: "global"}, 667 } 668 669 // Fetch the response 670 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 671 t.Fatalf("bad: %v", err) 672 } 673 674 // Check for the job in the FSM 675 out, err = state.JobByID(ws, job2.ID) 676 if err != nil { 677 t.Fatalf("err: %v", err) 678 } 679 if out == nil { 680 t.Fatalf("expected job") 681 } 682 if out.CreateIndex != resp.JobModifyIndex { 683 t.Fatalf("index mis-match") 684 } 685 if out.VaultToken != "" { 686 t.Fatalf("vault token not cleared") 687 } 688 } 689 690 func TestJobEndpoint_Evaluate(t *testing.T) { 691 s1 := testServer(t, func(c *Config) { 692 c.NumSchedulers = 0 // Prevent automatic dequeue 693 }) 694 defer s1.Shutdown() 695 codec := rpcClient(t, s1) 696 testutil.WaitForLeader(t, s1.RPC) 697 698 // Create the register request 699 job := mock.Job() 700 req := &structs.JobRegisterRequest{ 701 Job: job, 702 WriteRequest: structs.WriteRequest{Region: "global"}, 703 } 704 705 // Fetch the response 706 var resp structs.JobRegisterResponse 707 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 708 t.Fatalf("err: %v", err) 709 } 710 if resp.Index == 0 { 711 t.Fatalf("bad index: %d", resp.Index) 712 } 713 714 // Force a re-evaluation 715 reEval := &structs.JobEvaluateRequest{ 716 JobID: job.ID, 717 WriteRequest: structs.WriteRequest{Region: "global"}, 718 } 719 720 // Fetch the response 721 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil { 722 t.Fatalf("err: %v", err) 723 } 724 if resp.Index == 0 { 725 t.Fatalf("bad index: %d", resp.Index) 726 } 727 728 // Lookup the evaluation 729 state := s1.fsm.State() 730 ws := memdb.NewWatchSet() 731 eval, err := state.EvalByID(ws, resp.EvalID) 732 if err != nil { 733 t.Fatalf("err: %v", err) 734 } 735 if eval == nil { 736 t.Fatalf("expected eval") 737 } 738 if eval.CreateIndex != resp.EvalCreateIndex { 739 t.Fatalf("index mis-match") 740 } 741 742 if eval.Priority != job.Priority { 743 t.Fatalf("bad: %#v", eval) 744 } 745 if eval.Type != job.Type { 746 t.Fatalf("bad: %#v", eval) 747 } 748 if eval.TriggeredBy != structs.EvalTriggerJobRegister { 749 t.Fatalf("bad: %#v", eval) 750 } 751 if eval.JobID != job.ID { 752 t.Fatalf("bad: %#v", eval) 753 } 754 if eval.JobModifyIndex != resp.JobModifyIndex { 755 t.Fatalf("bad: %#v", eval) 756 } 757 if eval.Status != structs.EvalStatusPending { 758 t.Fatalf("bad: %#v", eval) 759 } 760 } 761 762 func TestJobEndpoint_Evaluate_Periodic(t *testing.T) { 763 s1 := testServer(t, func(c *Config) { 764 c.NumSchedulers = 0 // Prevent automatic dequeue 765 }) 766 defer s1.Shutdown() 767 codec := rpcClient(t, s1) 768 testutil.WaitForLeader(t, s1.RPC) 769 770 // Create the register request 771 job := mock.PeriodicJob() 772 req := &structs.JobRegisterRequest{ 773 Job: job, 774 WriteRequest: structs.WriteRequest{Region: "global"}, 775 } 776 777 // Fetch the response 778 var resp structs.JobRegisterResponse 779 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 780 t.Fatalf("err: %v", err) 781 } 782 if resp.JobModifyIndex == 0 { 783 t.Fatalf("bad index: %d", resp.Index) 784 } 785 786 // Force a re-evaluation 787 reEval := &structs.JobEvaluateRequest{ 788 JobID: job.ID, 789 WriteRequest: structs.WriteRequest{Region: "global"}, 790 } 791 792 // Fetch the response 793 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 794 t.Fatal("expect an err") 795 } 796 } 797 798 func TestJobEndpoint_Evaluate_ParameterizedJob(t *testing.T) { 799 s1 := testServer(t, func(c *Config) { 800 c.NumSchedulers = 0 // Prevent automatic dequeue 801 }) 802 defer s1.Shutdown() 803 codec := rpcClient(t, s1) 804 testutil.WaitForLeader(t, s1.RPC) 805 806 // Create the register request 807 job := mock.Job() 808 job.Type = structs.JobTypeBatch 809 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 810 req := &structs.JobRegisterRequest{ 811 Job: job, 812 WriteRequest: structs.WriteRequest{Region: "global"}, 813 } 814 815 // Fetch the response 816 var resp structs.JobRegisterResponse 817 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 818 t.Fatalf("err: %v", err) 819 } 820 if resp.JobModifyIndex == 0 { 821 t.Fatalf("bad index: %d", resp.Index) 822 } 823 824 // Force a re-evaluation 825 reEval := &structs.JobEvaluateRequest{ 826 JobID: job.ID, 827 WriteRequest: structs.WriteRequest{Region: "global"}, 828 } 829 830 // Fetch the response 831 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil { 832 t.Fatal("expect an err") 833 } 834 } 835 836 func TestJobEndpoint_Deregister(t *testing.T) { 837 s1 := testServer(t, func(c *Config) { 838 c.NumSchedulers = 0 // Prevent automatic dequeue 839 }) 840 defer s1.Shutdown() 841 codec := rpcClient(t, s1) 842 testutil.WaitForLeader(t, s1.RPC) 843 844 // Create the register request 845 job := mock.Job() 846 reg := &structs.JobRegisterRequest{ 847 Job: job, 848 WriteRequest: structs.WriteRequest{Region: "global"}, 849 } 850 851 // Fetch the response 852 var resp structs.JobRegisterResponse 853 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 854 t.Fatalf("err: %v", err) 855 } 856 857 // Deregister 858 dereg := &structs.JobDeregisterRequest{ 859 JobID: job.ID, 860 WriteRequest: structs.WriteRequest{Region: "global"}, 861 } 862 var resp2 structs.JobDeregisterResponse 863 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 864 t.Fatalf("err: %v", err) 865 } 866 if resp2.Index == 0 { 867 t.Fatalf("bad index: %d", resp2.Index) 868 } 869 870 // Check for the node in the FSM 871 ws := memdb.NewWatchSet() 872 state := s1.fsm.State() 873 out, err := state.JobByID(ws, job.ID) 874 if err != nil { 875 t.Fatalf("err: %v", err) 876 } 877 if out != nil { 878 t.Fatalf("unexpected job") 879 } 880 881 // Lookup the evaluation 882 eval, err := state.EvalByID(ws, resp2.EvalID) 883 if err != nil { 884 t.Fatalf("err: %v", err) 885 } 886 if eval == nil { 887 t.Fatalf("expected eval") 888 } 889 if eval.CreateIndex != resp2.EvalCreateIndex { 890 t.Fatalf("index mis-match") 891 } 892 893 if eval.Priority != structs.JobDefaultPriority { 894 t.Fatalf("bad: %#v", eval) 895 } 896 if eval.Type != structs.JobTypeService { 897 t.Fatalf("bad: %#v", eval) 898 } 899 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 900 t.Fatalf("bad: %#v", eval) 901 } 902 if eval.JobID != job.ID { 903 t.Fatalf("bad: %#v", eval) 904 } 905 if eval.JobModifyIndex != resp2.JobModifyIndex { 906 t.Fatalf("bad: %#v", eval) 907 } 908 if eval.Status != structs.EvalStatusPending { 909 t.Fatalf("bad: %#v", eval) 910 } 911 } 912 913 func TestJobEndpoint_Deregister_NonExistent(t *testing.T) { 914 s1 := testServer(t, func(c *Config) { 915 c.NumSchedulers = 0 // Prevent automatic dequeue 916 }) 917 defer s1.Shutdown() 918 codec := rpcClient(t, s1) 919 testutil.WaitForLeader(t, s1.RPC) 920 921 // Deregister 922 jobID := "foo" 923 dereg := &structs.JobDeregisterRequest{ 924 JobID: jobID, 925 WriteRequest: structs.WriteRequest{Region: "global"}, 926 } 927 var resp2 structs.JobDeregisterResponse 928 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 929 t.Fatalf("err: %v", err) 930 } 931 if resp2.JobModifyIndex == 0 { 932 t.Fatalf("bad index: %d", resp2.Index) 933 } 934 935 // Lookup the evaluation 936 state := s1.fsm.State() 937 ws := memdb.NewWatchSet() 938 eval, err := state.EvalByID(ws, resp2.EvalID) 939 if err != nil { 940 t.Fatalf("err: %v", err) 941 } 942 if eval == nil { 943 t.Fatalf("expected eval") 944 } 945 if eval.CreateIndex != resp2.EvalCreateIndex { 946 t.Fatalf("index mis-match") 947 } 948 949 if eval.Priority != structs.JobDefaultPriority { 950 t.Fatalf("bad: %#v", eval) 951 } 952 if eval.Type != structs.JobTypeService { 953 t.Fatalf("bad: %#v", eval) 954 } 955 if eval.TriggeredBy != structs.EvalTriggerJobDeregister { 956 t.Fatalf("bad: %#v", eval) 957 } 958 if eval.JobID != jobID { 959 t.Fatalf("bad: %#v", eval) 960 } 961 if eval.JobModifyIndex != resp2.JobModifyIndex { 962 t.Fatalf("bad: %#v", eval) 963 } 964 if eval.Status != structs.EvalStatusPending { 965 t.Fatalf("bad: %#v", eval) 966 } 967 } 968 969 func TestJobEndpoint_Deregister_Periodic(t *testing.T) { 970 s1 := testServer(t, func(c *Config) { 971 c.NumSchedulers = 0 // Prevent automatic dequeue 972 }) 973 defer s1.Shutdown() 974 codec := rpcClient(t, s1) 975 testutil.WaitForLeader(t, s1.RPC) 976 977 // Create the register request 978 job := mock.PeriodicJob() 979 reg := &structs.JobRegisterRequest{ 980 Job: job, 981 WriteRequest: structs.WriteRequest{Region: "global"}, 982 } 983 984 // Fetch the response 985 var resp structs.JobRegisterResponse 986 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 987 t.Fatalf("err: %v", err) 988 } 989 990 // Deregister 991 dereg := &structs.JobDeregisterRequest{ 992 JobID: job.ID, 993 WriteRequest: structs.WriteRequest{Region: "global"}, 994 } 995 var resp2 structs.JobDeregisterResponse 996 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 997 t.Fatalf("err: %v", err) 998 } 999 if resp2.JobModifyIndex == 0 { 1000 t.Fatalf("bad index: %d", resp2.Index) 1001 } 1002 1003 // Check for the node in the FSM 1004 state := s1.fsm.State() 1005 ws := memdb.NewWatchSet() 1006 out, err := state.JobByID(ws, job.ID) 1007 if err != nil { 1008 t.Fatalf("err: %v", err) 1009 } 1010 if out != nil { 1011 t.Fatalf("unexpected job") 1012 } 1013 1014 if resp.EvalID != "" { 1015 t.Fatalf("Deregister created an eval for a periodic job") 1016 } 1017 } 1018 1019 func TestJobEndpoint_Deregister_ParameterizedJob(t *testing.T) { 1020 s1 := testServer(t, func(c *Config) { 1021 c.NumSchedulers = 0 // Prevent automatic dequeue 1022 }) 1023 defer s1.Shutdown() 1024 codec := rpcClient(t, s1) 1025 testutil.WaitForLeader(t, s1.RPC) 1026 1027 // Create the register request 1028 job := mock.Job() 1029 job.Type = structs.JobTypeBatch 1030 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1031 reg := &structs.JobRegisterRequest{ 1032 Job: job, 1033 WriteRequest: structs.WriteRequest{Region: "global"}, 1034 } 1035 1036 // Fetch the response 1037 var resp structs.JobRegisterResponse 1038 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1039 t.Fatalf("err: %v", err) 1040 } 1041 1042 // Deregister 1043 dereg := &structs.JobDeregisterRequest{ 1044 JobID: job.ID, 1045 WriteRequest: structs.WriteRequest{Region: "global"}, 1046 } 1047 var resp2 structs.JobDeregisterResponse 1048 if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil { 1049 t.Fatalf("err: %v", err) 1050 } 1051 if resp2.JobModifyIndex == 0 { 1052 t.Fatalf("bad index: %d", resp2.Index) 1053 } 1054 1055 // Check for the node in the FSM 1056 state := s1.fsm.State() 1057 ws := memdb.NewWatchSet() 1058 out, err := state.JobByID(ws, job.ID) 1059 if err != nil { 1060 t.Fatalf("err: %v", err) 1061 } 1062 if out != nil { 1063 t.Fatalf("unexpected job") 1064 } 1065 1066 if resp.EvalID != "" { 1067 t.Fatalf("Deregister created an eval for a parameterized job") 1068 } 1069 } 1070 1071 func TestJobEndpoint_GetJob(t *testing.T) { 1072 s1 := testServer(t, nil) 1073 defer s1.Shutdown() 1074 codec := rpcClient(t, s1) 1075 testutil.WaitForLeader(t, s1.RPC) 1076 1077 // Create the register request 1078 job := mock.Job() 1079 reg := &structs.JobRegisterRequest{ 1080 Job: job, 1081 WriteRequest: structs.WriteRequest{Region: "global"}, 1082 } 1083 1084 // Fetch the response 1085 var resp structs.JobRegisterResponse 1086 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1087 t.Fatalf("err: %v", err) 1088 } 1089 job.CreateIndex = resp.JobModifyIndex 1090 job.ModifyIndex = resp.JobModifyIndex 1091 job.JobModifyIndex = resp.JobModifyIndex 1092 1093 // Lookup the job 1094 get := &structs.JobSpecificRequest{ 1095 JobID: job.ID, 1096 QueryOptions: structs.QueryOptions{Region: "global"}, 1097 } 1098 var resp2 structs.SingleJobResponse 1099 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 1100 t.Fatalf("err: %v", err) 1101 } 1102 if resp2.Index != resp.JobModifyIndex { 1103 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 1104 } 1105 1106 // Make a copy of the origin job and change the service name so that we can 1107 // do a deep equal with the response from the GET JOB Api 1108 j := job 1109 j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend" 1110 for tgix, tg := range j.TaskGroups { 1111 for tidx, t := range tg.Tasks { 1112 for sidx, service := range t.Services { 1113 for cidx, check := range service.Checks { 1114 check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name 1115 } 1116 } 1117 } 1118 } 1119 1120 if !reflect.DeepEqual(j, resp2.Job) { 1121 t.Fatalf("bad: %#v %#v", job, resp2.Job) 1122 } 1123 1124 // Lookup non-existing job 1125 get.JobID = "foobarbaz" 1126 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil { 1127 t.Fatalf("err: %v", err) 1128 } 1129 if resp2.Index != resp.JobModifyIndex { 1130 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 1131 } 1132 if resp2.Job != nil { 1133 t.Fatalf("unexpected job") 1134 } 1135 } 1136 1137 func TestJobEndpoint_GetJobSummary(t *testing.T) { 1138 s1 := testServer(t, func(c *Config) { 1139 c.NumSchedulers = 0 // Prevent automatic dequeue 1140 }) 1141 1142 defer s1.Shutdown() 1143 codec := rpcClient(t, s1) 1144 testutil.WaitForLeader(t, s1.RPC) 1145 1146 // Create the register request 1147 job := mock.Job() 1148 reg := &structs.JobRegisterRequest{ 1149 Job: job, 1150 WriteRequest: structs.WriteRequest{Region: "global"}, 1151 } 1152 1153 // Fetch the response 1154 var resp structs.JobRegisterResponse 1155 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil { 1156 t.Fatalf("err: %v", err) 1157 } 1158 job.CreateIndex = resp.JobModifyIndex 1159 job.ModifyIndex = resp.JobModifyIndex 1160 job.JobModifyIndex = resp.JobModifyIndex 1161 1162 // Lookup the job summary 1163 get := &structs.JobSummaryRequest{ 1164 JobID: job.ID, 1165 QueryOptions: structs.QueryOptions{Region: "global"}, 1166 } 1167 var resp2 structs.JobSummaryResponse 1168 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil { 1169 t.Fatalf("err: %v", err) 1170 } 1171 if resp2.Index != resp.JobModifyIndex { 1172 t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index) 1173 } 1174 1175 expectedJobSummary := structs.JobSummary{ 1176 JobID: job.ID, 1177 Summary: map[string]structs.TaskGroupSummary{ 1178 "web": structs.TaskGroupSummary{}, 1179 }, 1180 Children: new(structs.JobChildrenSummary), 1181 CreateIndex: job.CreateIndex, 1182 ModifyIndex: job.CreateIndex, 1183 } 1184 1185 if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) { 1186 t.Fatalf("exptected: %v, actual: %v", expectedJobSummary, resp2.JobSummary) 1187 } 1188 } 1189 1190 func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) { 1191 s1 := testServer(t, nil) 1192 defer s1.Shutdown() 1193 state := s1.fsm.State() 1194 codec := rpcClient(t, s1) 1195 testutil.WaitForLeader(t, s1.RPC) 1196 1197 // Create a job and insert it 1198 job1 := mock.Job() 1199 time.AfterFunc(200*time.Millisecond, func() { 1200 if err := state.UpsertJob(100, job1); err != nil { 1201 t.Fatalf("err: %v", err) 1202 } 1203 }) 1204 1205 // Ensure the job summary request gets fired 1206 req := &structs.JobSummaryRequest{ 1207 JobID: job1.ID, 1208 QueryOptions: structs.QueryOptions{ 1209 Region: "global", 1210 MinQueryIndex: 50, 1211 }, 1212 } 1213 var resp structs.JobSummaryResponse 1214 start := time.Now() 1215 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil { 1216 t.Fatalf("err: %v", err) 1217 } 1218 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1219 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1220 } 1221 1222 // Upsert an allocation for the job which should trigger the watch. 1223 time.AfterFunc(200*time.Millisecond, func() { 1224 alloc := mock.Alloc() 1225 alloc.JobID = job1.ID 1226 alloc.Job = job1 1227 if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil { 1228 t.Fatalf("err: %v", err) 1229 } 1230 }) 1231 req = &structs.JobSummaryRequest{ 1232 JobID: job1.ID, 1233 QueryOptions: structs.QueryOptions{ 1234 Region: "global", 1235 MinQueryIndex: 199, 1236 }, 1237 } 1238 start = time.Now() 1239 var resp1 structs.JobSummaryResponse 1240 start = time.Now() 1241 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil { 1242 t.Fatalf("err: %v", err) 1243 } 1244 1245 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1246 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1247 } 1248 if resp1.Index != 200 { 1249 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1250 } 1251 if resp1.JobSummary == nil { 1252 t.Fatalf("bad: %#v", resp) 1253 } 1254 1255 // Job delete fires watches 1256 time.AfterFunc(100*time.Millisecond, func() { 1257 if err := state.DeleteJob(300, job1.ID); err != nil { 1258 t.Fatalf("err: %v", err) 1259 } 1260 }) 1261 1262 req.QueryOptions.MinQueryIndex = 250 1263 start = time.Now() 1264 1265 var resp2 structs.SingleJobResponse 1266 if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil { 1267 t.Fatalf("err: %v", err) 1268 } 1269 1270 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1271 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1272 } 1273 if resp2.Index != 300 { 1274 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 1275 } 1276 if resp2.Job != nil { 1277 t.Fatalf("bad: %#v", resp2.Job) 1278 } 1279 } 1280 1281 func TestJobEndpoint_GetJob_Blocking(t *testing.T) { 1282 s1 := testServer(t, nil) 1283 defer s1.Shutdown() 1284 state := s1.fsm.State() 1285 codec := rpcClient(t, s1) 1286 testutil.WaitForLeader(t, s1.RPC) 1287 1288 // Create the jobs 1289 job1 := mock.Job() 1290 job2 := mock.Job() 1291 1292 // Upsert a job we are not interested in first. 1293 time.AfterFunc(100*time.Millisecond, func() { 1294 if err := state.UpsertJob(100, job1); err != nil { 1295 t.Fatalf("err: %v", err) 1296 } 1297 }) 1298 1299 // Upsert another job later which should trigger the watch. 1300 time.AfterFunc(200*time.Millisecond, func() { 1301 if err := state.UpsertJob(200, job2); err != nil { 1302 t.Fatalf("err: %v", err) 1303 } 1304 }) 1305 1306 req := &structs.JobSpecificRequest{ 1307 JobID: job2.ID, 1308 QueryOptions: structs.QueryOptions{ 1309 Region: "global", 1310 MinQueryIndex: 150, 1311 }, 1312 } 1313 start := time.Now() 1314 var resp structs.SingleJobResponse 1315 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil { 1316 t.Fatalf("err: %v", err) 1317 } 1318 1319 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1320 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1321 } 1322 if resp.Index != 200 { 1323 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1324 } 1325 if resp.Job == nil || resp.Job.ID != job2.ID { 1326 t.Fatalf("bad: %#v", resp.Job) 1327 } 1328 1329 // Job delete fires watches 1330 time.AfterFunc(100*time.Millisecond, func() { 1331 if err := state.DeleteJob(300, job2.ID); err != nil { 1332 t.Fatalf("err: %v", err) 1333 } 1334 }) 1335 1336 req.QueryOptions.MinQueryIndex = 250 1337 start = time.Now() 1338 1339 var resp2 structs.SingleJobResponse 1340 if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil { 1341 t.Fatalf("err: %v", err) 1342 } 1343 1344 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1345 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1346 } 1347 if resp2.Index != 300 { 1348 t.Fatalf("Bad index: %d %d", resp2.Index, 300) 1349 } 1350 if resp2.Job != nil { 1351 t.Fatalf("bad: %#v", resp2.Job) 1352 } 1353 } 1354 1355 func TestJobEndpoint_ListJobs(t *testing.T) { 1356 s1 := testServer(t, nil) 1357 defer s1.Shutdown() 1358 codec := rpcClient(t, s1) 1359 testutil.WaitForLeader(t, s1.RPC) 1360 1361 // Create the register request 1362 job := mock.Job() 1363 state := s1.fsm.State() 1364 err := state.UpsertJob(1000, job) 1365 if err != nil { 1366 t.Fatalf("err: %v", err) 1367 } 1368 1369 // Lookup the jobs 1370 get := &structs.JobListRequest{ 1371 QueryOptions: structs.QueryOptions{Region: "global"}, 1372 } 1373 var resp2 structs.JobListResponse 1374 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil { 1375 t.Fatalf("err: %v", err) 1376 } 1377 if resp2.Index != 1000 { 1378 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1379 } 1380 1381 if len(resp2.Jobs) != 1 { 1382 t.Fatalf("bad: %#v", resp2.Jobs) 1383 } 1384 if resp2.Jobs[0].ID != job.ID { 1385 t.Fatalf("bad: %#v", resp2.Jobs[0]) 1386 } 1387 1388 // Lookup the jobs by prefix 1389 get = &structs.JobListRequest{ 1390 QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]}, 1391 } 1392 var resp3 structs.JobListResponse 1393 if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil { 1394 t.Fatalf("err: %v", err) 1395 } 1396 if resp3.Index != 1000 { 1397 t.Fatalf("Bad index: %d %d", resp3.Index, 1000) 1398 } 1399 1400 if len(resp3.Jobs) != 1 { 1401 t.Fatalf("bad: %#v", resp3.Jobs) 1402 } 1403 if resp3.Jobs[0].ID != job.ID { 1404 t.Fatalf("bad: %#v", resp3.Jobs[0]) 1405 } 1406 } 1407 1408 func TestJobEndpoint_ListJobs_Blocking(t *testing.T) { 1409 s1 := testServer(t, nil) 1410 defer s1.Shutdown() 1411 state := s1.fsm.State() 1412 codec := rpcClient(t, s1) 1413 testutil.WaitForLeader(t, s1.RPC) 1414 1415 // Create the job 1416 job := mock.Job() 1417 1418 // Upsert job triggers watches 1419 time.AfterFunc(100*time.Millisecond, func() { 1420 if err := state.UpsertJob(100, job); err != nil { 1421 t.Fatalf("err: %v", err) 1422 } 1423 }) 1424 1425 req := &structs.JobListRequest{ 1426 QueryOptions: structs.QueryOptions{ 1427 Region: "global", 1428 MinQueryIndex: 50, 1429 }, 1430 } 1431 start := time.Now() 1432 var resp structs.JobListResponse 1433 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil { 1434 t.Fatalf("err: %v", err) 1435 } 1436 1437 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1438 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1439 } 1440 if resp.Index != 100 { 1441 t.Fatalf("Bad index: %d %d", resp.Index, 100) 1442 } 1443 if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID { 1444 t.Fatalf("bad: %#v", resp) 1445 } 1446 1447 // Job deletion triggers watches 1448 time.AfterFunc(100*time.Millisecond, func() { 1449 if err := state.DeleteJob(200, job.ID); err != nil { 1450 t.Fatalf("err: %v", err) 1451 } 1452 }) 1453 1454 req.MinQueryIndex = 150 1455 start = time.Now() 1456 var resp2 structs.JobListResponse 1457 if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil { 1458 t.Fatalf("err: %v", err) 1459 } 1460 1461 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 1462 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 1463 } 1464 if resp2.Index != 200 { 1465 t.Fatalf("Bad index: %d %d", resp2.Index, 200) 1466 } 1467 if len(resp2.Jobs) != 0 { 1468 t.Fatalf("bad: %#v", resp2) 1469 } 1470 } 1471 1472 func TestJobEndpoint_Allocations(t *testing.T) { 1473 s1 := testServer(t, nil) 1474 defer s1.Shutdown() 1475 codec := rpcClient(t, s1) 1476 testutil.WaitForLeader(t, s1.RPC) 1477 1478 // Create the register request 1479 alloc1 := mock.Alloc() 1480 alloc2 := mock.Alloc() 1481 alloc2.JobID = alloc1.JobID 1482 state := s1.fsm.State() 1483 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 1484 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 1485 err := state.UpsertAllocs(1000, 1486 []*structs.Allocation{alloc1, alloc2}) 1487 if err != nil { 1488 t.Fatalf("err: %v", err) 1489 } 1490 1491 // Lookup the jobs 1492 get := &structs.JobSpecificRequest{ 1493 JobID: alloc1.JobID, 1494 QueryOptions: structs.QueryOptions{Region: "global"}, 1495 } 1496 var resp2 structs.JobAllocationsResponse 1497 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil { 1498 t.Fatalf("err: %v", err) 1499 } 1500 if resp2.Index != 1000 { 1501 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1502 } 1503 1504 if len(resp2.Allocations) != 2 { 1505 t.Fatalf("bad: %#v", resp2.Allocations) 1506 } 1507 } 1508 1509 func TestJobEndpoint_Allocations_Blocking(t *testing.T) { 1510 s1 := testServer(t, nil) 1511 defer s1.Shutdown() 1512 codec := rpcClient(t, s1) 1513 testutil.WaitForLeader(t, s1.RPC) 1514 1515 // Create the register request 1516 alloc1 := mock.Alloc() 1517 alloc2 := mock.Alloc() 1518 alloc2.JobID = "job1" 1519 state := s1.fsm.State() 1520 1521 // First upsert an unrelated alloc 1522 time.AfterFunc(100*time.Millisecond, func() { 1523 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 1524 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 1525 if err != nil { 1526 t.Fatalf("err: %v", err) 1527 } 1528 }) 1529 1530 // Upsert an alloc for the job we are interested in later 1531 time.AfterFunc(200*time.Millisecond, func() { 1532 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 1533 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 1534 if err != nil { 1535 t.Fatalf("err: %v", err) 1536 } 1537 }) 1538 1539 // Lookup the jobs 1540 get := &structs.JobSpecificRequest{ 1541 JobID: "job1", 1542 QueryOptions: structs.QueryOptions{ 1543 Region: "global", 1544 MinQueryIndex: 150, 1545 }, 1546 } 1547 var resp structs.JobAllocationsResponse 1548 start := time.Now() 1549 if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil { 1550 t.Fatalf("err: %v", err) 1551 } 1552 1553 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1554 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1555 } 1556 if resp.Index != 200 { 1557 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1558 } 1559 if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" { 1560 t.Fatalf("bad: %#v", resp.Allocations) 1561 } 1562 } 1563 1564 func TestJobEndpoint_Evaluations(t *testing.T) { 1565 s1 := testServer(t, nil) 1566 defer s1.Shutdown() 1567 codec := rpcClient(t, s1) 1568 testutil.WaitForLeader(t, s1.RPC) 1569 1570 // Create the register request 1571 eval1 := mock.Eval() 1572 eval2 := mock.Eval() 1573 eval2.JobID = eval1.JobID 1574 state := s1.fsm.State() 1575 err := state.UpsertEvals(1000, 1576 []*structs.Evaluation{eval1, eval2}) 1577 if err != nil { 1578 t.Fatalf("err: %v", err) 1579 } 1580 1581 // Lookup the jobs 1582 get := &structs.JobSpecificRequest{ 1583 JobID: eval1.JobID, 1584 QueryOptions: structs.QueryOptions{Region: "global"}, 1585 } 1586 var resp2 structs.JobEvaluationsResponse 1587 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil { 1588 t.Fatalf("err: %v", err) 1589 } 1590 if resp2.Index != 1000 { 1591 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 1592 } 1593 1594 if len(resp2.Evaluations) != 2 { 1595 t.Fatalf("bad: %#v", resp2.Evaluations) 1596 } 1597 } 1598 1599 func TestJobEndpoint_Evaluations_Blocking(t *testing.T) { 1600 s1 := testServer(t, nil) 1601 defer s1.Shutdown() 1602 codec := rpcClient(t, s1) 1603 testutil.WaitForLeader(t, s1.RPC) 1604 1605 // Create the register request 1606 eval1 := mock.Eval() 1607 eval2 := mock.Eval() 1608 eval2.JobID = "job1" 1609 state := s1.fsm.State() 1610 1611 // First upsert an unrelated eval 1612 time.AfterFunc(100*time.Millisecond, func() { 1613 err := state.UpsertEvals(100, []*structs.Evaluation{eval1}) 1614 if err != nil { 1615 t.Fatalf("err: %v", err) 1616 } 1617 }) 1618 1619 // Upsert an eval for the job we are interested in later 1620 time.AfterFunc(200*time.Millisecond, func() { 1621 err := state.UpsertEvals(200, []*structs.Evaluation{eval2}) 1622 if err != nil { 1623 t.Fatalf("err: %v", err) 1624 } 1625 }) 1626 1627 // Lookup the jobs 1628 get := &structs.JobSpecificRequest{ 1629 JobID: "job1", 1630 QueryOptions: structs.QueryOptions{ 1631 Region: "global", 1632 MinQueryIndex: 150, 1633 }, 1634 } 1635 var resp structs.JobEvaluationsResponse 1636 start := time.Now() 1637 if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil { 1638 t.Fatalf("err: %v", err) 1639 } 1640 1641 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1642 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1643 } 1644 if resp.Index != 200 { 1645 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1646 } 1647 if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" { 1648 t.Fatalf("bad: %#v", resp.Evaluations) 1649 } 1650 } 1651 1652 func TestJobEndpoint_Plan_WithDiff(t *testing.T) { 1653 s1 := testServer(t, func(c *Config) { 1654 c.NumSchedulers = 0 // Prevent automatic dequeue 1655 }) 1656 defer s1.Shutdown() 1657 codec := rpcClient(t, s1) 1658 testutil.WaitForLeader(t, s1.RPC) 1659 1660 // Create the register request 1661 job := mock.Job() 1662 req := &structs.JobRegisterRequest{ 1663 Job: job, 1664 WriteRequest: structs.WriteRequest{Region: "global"}, 1665 } 1666 1667 // Fetch the response 1668 var resp structs.JobRegisterResponse 1669 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1670 t.Fatalf("err: %v", err) 1671 } 1672 if resp.Index == 0 { 1673 t.Fatalf("bad index: %d", resp.Index) 1674 } 1675 1676 // Create a plan request 1677 planReq := &structs.JobPlanRequest{ 1678 Job: job, 1679 Diff: true, 1680 WriteRequest: structs.WriteRequest{Region: "global"}, 1681 } 1682 1683 // Fetch the response 1684 var planResp structs.JobPlanResponse 1685 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 1686 t.Fatalf("err: %v", err) 1687 } 1688 1689 // Check the response 1690 if planResp.JobModifyIndex == 0 { 1691 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 1692 } 1693 if planResp.Annotations == nil { 1694 t.Fatalf("no annotations") 1695 } 1696 if planResp.Diff == nil { 1697 t.Fatalf("no diff") 1698 } 1699 if len(planResp.FailedTGAllocs) == 0 { 1700 t.Fatalf("no failed task group alloc metrics") 1701 } 1702 } 1703 1704 func TestJobEndpoint_Plan_NoDiff(t *testing.T) { 1705 s1 := testServer(t, func(c *Config) { 1706 c.NumSchedulers = 0 // Prevent automatic dequeue 1707 }) 1708 defer s1.Shutdown() 1709 codec := rpcClient(t, s1) 1710 testutil.WaitForLeader(t, s1.RPC) 1711 1712 // Create the register request 1713 job := mock.Job() 1714 req := &structs.JobRegisterRequest{ 1715 Job: job, 1716 WriteRequest: structs.WriteRequest{Region: "global"}, 1717 } 1718 1719 // Fetch the response 1720 var resp structs.JobRegisterResponse 1721 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1722 t.Fatalf("err: %v", err) 1723 } 1724 if resp.Index == 0 { 1725 t.Fatalf("bad index: %d", resp.Index) 1726 } 1727 1728 // Create a plan request 1729 planReq := &structs.JobPlanRequest{ 1730 Job: job, 1731 Diff: false, 1732 WriteRequest: structs.WriteRequest{Region: "global"}, 1733 } 1734 1735 // Fetch the response 1736 var planResp structs.JobPlanResponse 1737 if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil { 1738 t.Fatalf("err: %v", err) 1739 } 1740 1741 // Check the response 1742 if planResp.JobModifyIndex == 0 { 1743 t.Fatalf("bad cas: %d", planResp.JobModifyIndex) 1744 } 1745 if planResp.Annotations == nil { 1746 t.Fatalf("no annotations") 1747 } 1748 if planResp.Diff != nil { 1749 t.Fatalf("got diff") 1750 } 1751 if len(planResp.FailedTGAllocs) == 0 { 1752 t.Fatalf("no failed task group alloc metrics") 1753 } 1754 } 1755 1756 func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) { 1757 s1 := testServer(t, func(c *Config) { 1758 c.NumSchedulers = 0 // Prevent automatic dequeue 1759 }) 1760 defer s1.Shutdown() 1761 codec := rpcClient(t, s1) 1762 testutil.WaitForLeader(t, s1.RPC) 1763 1764 // Enable vault 1765 tr, f := true, false 1766 s1.config.VaultConfig.Enabled = &tr 1767 s1.config.VaultConfig.AllowUnauthenticated = &f 1768 1769 // Replace the Vault Client on the server 1770 tvc := &TestVaultClient{} 1771 s1.vault = tvc 1772 1773 policy := "foo" 1774 goodToken := structs.GenerateUUID() 1775 goodPolicies := []string{"foo", "bar", "baz"} 1776 tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies) 1777 1778 // Create the register request with a job asking for a vault policy 1779 job := mock.Job() 1780 job.VaultToken = goodToken 1781 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 1782 Policies: []string{policy}, 1783 ChangeMode: structs.VaultChangeModeRestart, 1784 } 1785 req := &structs.JobRegisterRequest{ 1786 Job: job, 1787 WriteRequest: structs.WriteRequest{Region: "global"}, 1788 } 1789 1790 // Fetch the response 1791 var resp structs.JobRegisterResponse 1792 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1793 t.Fatalf("bad: %v", err) 1794 } 1795 1796 // Check for the job in the FSM 1797 state := s1.fsm.State() 1798 ws := memdb.NewWatchSet() 1799 out, err := state.JobByID(ws, job.ID) 1800 if err != nil { 1801 t.Fatalf("err: %v", err) 1802 } 1803 if out == nil { 1804 t.Fatalf("expected job") 1805 } 1806 if out.CreateIndex != resp.JobModifyIndex { 1807 t.Fatalf("index mis-match") 1808 } 1809 1810 // Check that there is an implicit vault constraint 1811 constraints := out.TaskGroups[0].Constraints 1812 if len(constraints) != 1 { 1813 t.Fatalf("Expected an implicit constraint") 1814 } 1815 1816 if !constraints[0].Equal(vaultConstraint) { 1817 t.Fatalf("Expected implicit vault constraint") 1818 } 1819 } 1820 1821 func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) { 1822 s1 := testServer(t, func(c *Config) { 1823 c.NumSchedulers = 0 // Prevent automatic dequeue 1824 }) 1825 defer s1.Shutdown() 1826 codec := rpcClient(t, s1) 1827 testutil.WaitForLeader(t, s1.RPC) 1828 1829 // Create the register request with a job asking for a template that sends a 1830 // signal 1831 job := mock.Job() 1832 signal := "SIGUSR1" 1833 job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{ 1834 &structs.Template{ 1835 SourcePath: "foo", 1836 DestPath: "bar", 1837 ChangeMode: structs.TemplateChangeModeSignal, 1838 ChangeSignal: signal, 1839 }, 1840 } 1841 req := &structs.JobRegisterRequest{ 1842 Job: job, 1843 WriteRequest: structs.WriteRequest{Region: "global"}, 1844 } 1845 1846 // Fetch the response 1847 var resp structs.JobRegisterResponse 1848 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil { 1849 t.Fatalf("bad: %v", err) 1850 } 1851 1852 // Check for the job in the FSM 1853 state := s1.fsm.State() 1854 ws := memdb.NewWatchSet() 1855 out, err := state.JobByID(ws, job.ID) 1856 if err != nil { 1857 t.Fatalf("err: %v", err) 1858 } 1859 if out == nil { 1860 t.Fatalf("expected job") 1861 } 1862 if out.CreateIndex != resp.JobModifyIndex { 1863 t.Fatalf("index mis-match") 1864 } 1865 1866 // Check that there is an implicit signal constraint 1867 constraints := out.TaskGroups[0].Constraints 1868 if len(constraints) != 1 { 1869 t.Fatalf("Expected an implicit constraint") 1870 } 1871 1872 sigConstraint := getSignalConstraint([]string{signal}) 1873 1874 if !constraints[0].Equal(sigConstraint) { 1875 t.Fatalf("Expected implicit vault constraint") 1876 } 1877 } 1878 1879 func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) { 1880 // Create a mock job with an invalid config 1881 job := mock.Job() 1882 job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{ 1883 "foo": "bar", 1884 } 1885 1886 if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "-> config") { 1887 t.Fatalf("Expected config error; got %v", err) 1888 } 1889 } 1890 1891 func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) { 1892 // Create a mock job that wants to send a signal to a driver that can't 1893 job := mock.Job() 1894 job.TaskGroups[0].Tasks[0].Driver = "qemu" 1895 job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{ 1896 Policies: []string{"foo"}, 1897 ChangeMode: structs.VaultChangeModeSignal, 1898 ChangeSignal: "SIGUSR1", 1899 } 1900 1901 if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "support sending signals") { 1902 t.Fatalf("Expected signal feasibility error; got %v", err) 1903 } 1904 } 1905 1906 func TestJobEndpoint_Dispatch(t *testing.T) { 1907 1908 // No requirements 1909 d1 := mock.Job() 1910 d1.Type = structs.JobTypeBatch 1911 d1.ParameterizedJob = &structs.ParameterizedJobConfig{} 1912 1913 // Require input data 1914 d2 := mock.Job() 1915 d2.Type = structs.JobTypeBatch 1916 d2.ParameterizedJob = &structs.ParameterizedJobConfig{ 1917 Payload: structs.DispatchPayloadRequired, 1918 } 1919 1920 // Disallow input data 1921 d3 := mock.Job() 1922 d3.Type = structs.JobTypeBatch 1923 d3.ParameterizedJob = &structs.ParameterizedJobConfig{ 1924 Payload: structs.DispatchPayloadForbidden, 1925 } 1926 1927 // Require meta 1928 d4 := mock.Job() 1929 d4.Type = structs.JobTypeBatch 1930 d4.ParameterizedJob = &structs.ParameterizedJobConfig{ 1931 MetaRequired: []string{"foo", "bar"}, 1932 } 1933 1934 // Optional meta 1935 d5 := mock.Job() 1936 d5.Type = structs.JobTypeBatch 1937 d5.ParameterizedJob = &structs.ParameterizedJobConfig{ 1938 MetaOptional: []string{"foo", "bar"}, 1939 } 1940 1941 // Periodic dispatch job 1942 d6 := mock.PeriodicJob() 1943 d6.ParameterizedJob = &structs.ParameterizedJobConfig{} 1944 1945 reqNoInputNoMeta := &structs.JobDispatchRequest{} 1946 reqInputDataNoMeta := &structs.JobDispatchRequest{ 1947 Payload: []byte("hello world"), 1948 } 1949 reqNoInputDataMeta := &structs.JobDispatchRequest{ 1950 Meta: map[string]string{ 1951 "foo": "f1", 1952 "bar": "f2", 1953 }, 1954 } 1955 reqInputDataMeta := &structs.JobDispatchRequest{ 1956 Payload: []byte("hello world"), 1957 Meta: map[string]string{ 1958 "foo": "f1", 1959 "bar": "f2", 1960 }, 1961 } 1962 reqBadMeta := &structs.JobDispatchRequest{ 1963 Payload: []byte("hello world"), 1964 Meta: map[string]string{ 1965 "foo": "f1", 1966 "bar": "f2", 1967 "baz": "f3", 1968 }, 1969 } 1970 reqInputDataTooLarge := &structs.JobDispatchRequest{ 1971 Payload: make([]byte, DispatchPayloadSizeLimit+100), 1972 } 1973 1974 type testCase struct { 1975 name string 1976 parameterizedJob *structs.Job 1977 dispatchReq *structs.JobDispatchRequest 1978 noEval bool 1979 err bool 1980 errStr string 1981 } 1982 cases := []testCase{ 1983 { 1984 name: "optional input data w/ data", 1985 parameterizedJob: d1, 1986 dispatchReq: reqInputDataNoMeta, 1987 err: false, 1988 }, 1989 { 1990 name: "optional input data w/o data", 1991 parameterizedJob: d1, 1992 dispatchReq: reqNoInputNoMeta, 1993 err: false, 1994 }, 1995 { 1996 name: "require input data w/ data", 1997 parameterizedJob: d2, 1998 dispatchReq: reqInputDataNoMeta, 1999 err: false, 2000 }, 2001 { 2002 name: "require input data w/o data", 2003 parameterizedJob: d2, 2004 dispatchReq: reqNoInputNoMeta, 2005 err: true, 2006 errStr: "not provided but required", 2007 }, 2008 { 2009 name: "disallow input data w/o data", 2010 parameterizedJob: d3, 2011 dispatchReq: reqNoInputNoMeta, 2012 err: false, 2013 }, 2014 { 2015 name: "disallow input data w/ data", 2016 parameterizedJob: d3, 2017 dispatchReq: reqInputDataNoMeta, 2018 err: true, 2019 errStr: "provided but forbidden", 2020 }, 2021 { 2022 name: "require meta w/ meta", 2023 parameterizedJob: d4, 2024 dispatchReq: reqInputDataMeta, 2025 err: false, 2026 }, 2027 { 2028 name: "require meta w/o meta", 2029 parameterizedJob: d4, 2030 dispatchReq: reqNoInputNoMeta, 2031 err: true, 2032 errStr: "did not provide required meta keys", 2033 }, 2034 { 2035 name: "optional meta w/ meta", 2036 parameterizedJob: d5, 2037 dispatchReq: reqNoInputDataMeta, 2038 err: false, 2039 }, 2040 { 2041 name: "optional meta w/o meta", 2042 parameterizedJob: d5, 2043 dispatchReq: reqNoInputNoMeta, 2044 err: false, 2045 }, 2046 { 2047 name: "optional meta w/ bad meta", 2048 parameterizedJob: d5, 2049 dispatchReq: reqBadMeta, 2050 err: true, 2051 errStr: "unpermitted metadata keys", 2052 }, 2053 { 2054 name: "optional input w/ too big of input", 2055 parameterizedJob: d1, 2056 dispatchReq: reqInputDataTooLarge, 2057 err: true, 2058 errStr: "Payload exceeds maximum size", 2059 }, 2060 { 2061 name: "periodic job dispatched, ensure no eval", 2062 parameterizedJob: d6, 2063 dispatchReq: reqNoInputNoMeta, 2064 noEval: true, 2065 }, 2066 } 2067 2068 for _, tc := range cases { 2069 t.Run(tc.name, func(t *testing.T) { 2070 s1 := testServer(t, func(c *Config) { 2071 c.NumSchedulers = 0 // Prevent automatic dequeue 2072 }) 2073 defer s1.Shutdown() 2074 codec := rpcClient(t, s1) 2075 testutil.WaitForLeader(t, s1.RPC) 2076 2077 // Create the register request 2078 regReq := &structs.JobRegisterRequest{ 2079 Job: tc.parameterizedJob, 2080 WriteRequest: structs.WriteRequest{Region: "global"}, 2081 } 2082 2083 // Fetch the response 2084 var regResp structs.JobRegisterResponse 2085 if err := msgpackrpc.CallWithCodec(codec, "Job.Register", regReq, ®Resp); err != nil { 2086 t.Fatalf("err: %v", err) 2087 } 2088 2089 // Now try to dispatch 2090 tc.dispatchReq.JobID = tc.parameterizedJob.ID 2091 tc.dispatchReq.WriteRequest = structs.WriteRequest{Region: "global"} 2092 2093 var dispatchResp structs.JobDispatchResponse 2094 dispatchErr := msgpackrpc.CallWithCodec(codec, "Job.Dispatch", tc.dispatchReq, &dispatchResp) 2095 2096 if dispatchErr == nil { 2097 if tc.err { 2098 t.Fatalf("Expected error: %v", dispatchErr) 2099 } 2100 2101 // Check that we got an eval and job id back 2102 switch dispatchResp.EvalID { 2103 case "": 2104 if !tc.noEval { 2105 t.Fatalf("Bad response") 2106 } 2107 default: 2108 if tc.noEval { 2109 t.Fatalf("Got eval %q", dispatchResp.EvalID) 2110 } 2111 } 2112 2113 if dispatchResp.DispatchedJobID == "" { 2114 t.Fatalf("Bad response") 2115 } 2116 2117 state := s1.fsm.State() 2118 ws := memdb.NewWatchSet() 2119 out, err := state.JobByID(ws, dispatchResp.DispatchedJobID) 2120 if err != nil { 2121 t.Fatalf("err: %v", err) 2122 } 2123 if out == nil { 2124 t.Fatalf("expected job") 2125 } 2126 if out.CreateIndex != dispatchResp.JobCreateIndex { 2127 t.Fatalf("index mis-match") 2128 } 2129 if out.ParentID != tc.parameterizedJob.ID { 2130 t.Fatalf("bad parent ID") 2131 } 2132 2133 if tc.noEval { 2134 return 2135 } 2136 2137 // Lookup the evaluation 2138 eval, err := state.EvalByID(ws, dispatchResp.EvalID) 2139 if err != nil { 2140 t.Fatalf("err: %v", err) 2141 } 2142 2143 if eval == nil { 2144 t.Fatalf("expected eval") 2145 } 2146 if eval.CreateIndex != dispatchResp.EvalCreateIndex { 2147 t.Fatalf("index mis-match") 2148 } 2149 } else { 2150 if !tc.err { 2151 t.Fatalf("Got unexpected error: %v", dispatchErr) 2152 } else if !strings.Contains(dispatchErr.Error(), tc.errStr) { 2153 t.Fatalf("Expected err to include %q; got %v", tc.errStr, dispatchErr) 2154 } 2155 } 2156 }) 2157 } 2158 }