github.com/adityamillind98/nomad@v0.11.8/command/agent/job_endpoint_test.go (about) 1 package agent 2 3 import ( 4 "net/http" 5 "net/http/httptest" 6 "reflect" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/golang/snappy" 12 "github.com/hashicorp/nomad/api" 13 "github.com/hashicorp/nomad/helper" 14 "github.com/hashicorp/nomad/nomad/mock" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/kr/pretty" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestHTTP_JobsList(t *testing.T) { 22 t.Parallel() 23 httpTest(t, nil, func(s *TestAgent) { 24 for i := 0; i < 3; i++ { 25 // Create the job 26 job := mock.Job() 27 args := structs.JobRegisterRequest{ 28 Job: job, 29 WriteRequest: structs.WriteRequest{ 30 Region: "global", 31 Namespace: structs.DefaultNamespace, 32 }, 33 } 34 var resp structs.JobRegisterResponse 35 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 36 t.Fatalf("err: %v", err) 37 } 38 } 39 40 // Make the HTTP request 41 req, err := http.NewRequest("GET", "/v1/jobs", nil) 42 if err != nil { 43 t.Fatalf("err: %v", err) 44 } 45 respW := httptest.NewRecorder() 46 47 // Make the request 48 obj, err := s.Server.JobsRequest(respW, req) 49 if err != nil { 50 t.Fatalf("err: %v", err) 51 } 52 53 // Check for the index 54 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 55 t.Fatalf("missing index") 56 } 57 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 58 t.Fatalf("missing known leader") 59 } 60 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 61 t.Fatalf("missing last contact") 62 } 63 64 // Check the job 65 j := obj.([]*structs.JobListStub) 66 if len(j) != 3 { 67 t.Fatalf("bad: %#v", j) 68 } 69 }) 70 } 71 72 func TestHTTP_PrefixJobsList(t *testing.T) { 73 ids := []string{ 74 "aaaaaaaa-e8f7-fd38-c855-ab94ceb89706", 75 "aabbbbbb-e8f7-fd38-c855-ab94ceb89706", 76 "aabbcccc-e8f7-fd38-c855-ab94ceb89706", 77 } 78 t.Parallel() 79 httpTest(t, nil, func(s *TestAgent) { 80 for i := 0; i < 3; i++ { 81 // Create the job 82 job := mock.Job() 83 job.ID = ids[i] 84 job.TaskGroups[0].Count = 1 85 args := structs.JobRegisterRequest{ 86 Job: job, 87 WriteRequest: structs.WriteRequest{ 88 Region: "global", 89 Namespace: structs.DefaultNamespace, 90 }, 91 } 92 var resp structs.JobRegisterResponse 93 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 94 t.Fatalf("err: %v", err) 95 } 96 } 97 98 // Make the HTTP request 99 req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil) 100 if err != nil { 101 t.Fatalf("err: %v", err) 102 } 103 respW := httptest.NewRecorder() 104 105 // Make the request 106 obj, err := s.Server.JobsRequest(respW, req) 107 if err != nil { 108 t.Fatalf("err: %v", err) 109 } 110 111 // Check for the index 112 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 113 t.Fatalf("missing index") 114 } 115 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 116 t.Fatalf("missing known leader") 117 } 118 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 119 t.Fatalf("missing last contact") 120 } 121 122 // Check the job 123 j := obj.([]*structs.JobListStub) 124 if len(j) != 2 { 125 t.Fatalf("bad: %#v", j) 126 } 127 }) 128 } 129 130 func TestHTTP_JobsRegister(t *testing.T) { 131 t.Parallel() 132 httpTest(t, nil, func(s *TestAgent) { 133 // Create the job 134 job := MockJob() 135 args := api.JobRegisterRequest{ 136 Job: job, 137 WriteRequest: api.WriteRequest{Region: "global"}, 138 } 139 buf := encodeReq(args) 140 141 // Make the HTTP request 142 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 143 if err != nil { 144 t.Fatalf("err: %v", err) 145 } 146 respW := httptest.NewRecorder() 147 148 // Make the request 149 obj, err := s.Server.JobsRequest(respW, req) 150 if err != nil { 151 t.Fatalf("err: %v", err) 152 } 153 154 // Check the response 155 dereg := obj.(structs.JobRegisterResponse) 156 if dereg.EvalID == "" { 157 t.Fatalf("bad: %v", dereg) 158 } 159 160 // Check for the index 161 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 162 t.Fatalf("missing index") 163 } 164 165 // Check the job is registered 166 getReq := structs.JobSpecificRequest{ 167 JobID: *job.ID, 168 QueryOptions: structs.QueryOptions{ 169 Region: "global", 170 Namespace: structs.DefaultNamespace, 171 }, 172 } 173 var getResp structs.SingleJobResponse 174 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 175 t.Fatalf("err: %v", err) 176 } 177 178 if getResp.Job == nil { 179 t.Fatalf("job does not exist") 180 } 181 }) 182 } 183 184 // Test that ACL token is properly threaded through to the RPC endpoint 185 func TestHTTP_JobsRegister_ACL(t *testing.T) { 186 t.Parallel() 187 httpACLTest(t, nil, func(s *TestAgent) { 188 // Create the job 189 job := MockJob() 190 args := api.JobRegisterRequest{ 191 Job: job, 192 WriteRequest: api.WriteRequest{ 193 Region: "global", 194 }, 195 } 196 buf := encodeReq(args) 197 198 // Make the HTTP request 199 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 200 if err != nil { 201 t.Fatalf("err: %v", err) 202 } 203 respW := httptest.NewRecorder() 204 setToken(req, s.RootToken) 205 206 // Make the request 207 obj, err := s.Server.JobsRequest(respW, req) 208 if err != nil { 209 t.Fatalf("err: %v", err) 210 } 211 assert.NotNil(t, obj) 212 }) 213 } 214 215 func TestHTTP_JobsRegister_Defaulting(t *testing.T) { 216 t.Parallel() 217 httpTest(t, nil, func(s *TestAgent) { 218 // Create the job 219 job := MockJob() 220 221 // Do not set its priority 222 job.Priority = nil 223 224 args := api.JobRegisterRequest{ 225 Job: job, 226 WriteRequest: api.WriteRequest{Region: "global"}, 227 } 228 buf := encodeReq(args) 229 230 // Make the HTTP request 231 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 232 if err != nil { 233 t.Fatalf("err: %v", err) 234 } 235 respW := httptest.NewRecorder() 236 237 // Make the request 238 obj, err := s.Server.JobsRequest(respW, req) 239 if err != nil { 240 t.Fatalf("err: %v", err) 241 } 242 243 // Check the response 244 dereg := obj.(structs.JobRegisterResponse) 245 if dereg.EvalID == "" { 246 t.Fatalf("bad: %v", dereg) 247 } 248 249 // Check for the index 250 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 251 t.Fatalf("missing index") 252 } 253 254 // Check the job is registered 255 getReq := structs.JobSpecificRequest{ 256 JobID: *job.ID, 257 QueryOptions: structs.QueryOptions{ 258 Region: "global", 259 Namespace: structs.DefaultNamespace, 260 }, 261 } 262 var getResp structs.SingleJobResponse 263 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 264 t.Fatalf("err: %v", err) 265 } 266 267 if getResp.Job == nil { 268 t.Fatalf("job does not exist") 269 } 270 if getResp.Job.Priority != 50 { 271 t.Fatalf("job didn't get defaulted") 272 } 273 }) 274 } 275 276 func TestHTTP_JobsParse(t *testing.T) { 277 t.Parallel() 278 httpTest(t, nil, func(s *TestAgent) { 279 buf := encodeReq(api.JobsParseRequest{JobHCL: mock.HCL()}) 280 req, err := http.NewRequest("POST", "/v1/jobs/parse", buf) 281 if err != nil { 282 t.Fatalf("err: %v", err) 283 } 284 285 respW := httptest.NewRecorder() 286 287 obj, err := s.Server.JobsParseRequest(respW, req) 288 if err != nil { 289 t.Fatalf("err: %v", err) 290 } 291 if obj == nil { 292 t.Fatal("response should not be nil") 293 } 294 295 job := obj.(*api.Job) 296 expected := mock.Job() 297 if job.Name == nil || *job.Name != expected.Name { 298 t.Fatalf("job name is '%s', expected '%s'", *job.Name, expected.Name) 299 } 300 301 if job.Datacenters == nil || 302 job.Datacenters[0] != expected.Datacenters[0] { 303 t.Fatalf("job datacenters is '%s', expected '%s'", 304 job.Datacenters[0], expected.Datacenters[0]) 305 } 306 }) 307 } 308 func TestHTTP_JobQuery(t *testing.T) { 309 t.Parallel() 310 httpTest(t, nil, func(s *TestAgent) { 311 // Create the job 312 job := mock.Job() 313 args := structs.JobRegisterRequest{ 314 Job: job, 315 WriteRequest: structs.WriteRequest{ 316 Region: "global", 317 Namespace: structs.DefaultNamespace, 318 }, 319 } 320 var resp structs.JobRegisterResponse 321 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 322 t.Fatalf("err: %v", err) 323 } 324 325 // Make the HTTP request 326 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 327 if err != nil { 328 t.Fatalf("err: %v", err) 329 } 330 respW := httptest.NewRecorder() 331 332 // Make the request 333 obj, err := s.Server.JobSpecificRequest(respW, req) 334 if err != nil { 335 t.Fatalf("err: %v", err) 336 } 337 338 // Check for the index 339 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 340 t.Fatalf("missing index") 341 } 342 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 343 t.Fatalf("missing known leader") 344 } 345 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 346 t.Fatalf("missing last contact") 347 } 348 349 // Check the job 350 j := obj.(*structs.Job) 351 if j.ID != job.ID { 352 t.Fatalf("bad: %#v", j) 353 } 354 }) 355 } 356 357 func TestHTTP_JobQuery_Payload(t *testing.T) { 358 t.Parallel() 359 httpTest(t, nil, func(s *TestAgent) { 360 // Create the job 361 job := mock.Job() 362 363 // Insert Payload compressed 364 expected := []byte("hello world") 365 compressed := snappy.Encode(nil, expected) 366 job.Payload = compressed 367 368 // Directly manipulate the state 369 state := s.Agent.server.State() 370 if err := state.UpsertJob(1000, job); err != nil { 371 t.Fatalf("Failed to upsert job: %v", err) 372 } 373 374 // Make the HTTP request 375 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 376 if err != nil { 377 t.Fatalf("err: %v", err) 378 } 379 respW := httptest.NewRecorder() 380 381 // Make the request 382 obj, err := s.Server.JobSpecificRequest(respW, req) 383 if err != nil { 384 t.Fatalf("err: %v", err) 385 } 386 387 // Check for the index 388 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 389 t.Fatalf("missing index") 390 } 391 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 392 t.Fatalf("missing known leader") 393 } 394 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 395 t.Fatalf("missing last contact") 396 } 397 398 // Check the job 399 j := obj.(*structs.Job) 400 if j.ID != job.ID { 401 t.Fatalf("bad: %#v", j) 402 } 403 404 // Check the payload is decompressed 405 if !reflect.DeepEqual(j.Payload, expected) { 406 t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected) 407 } 408 }) 409 } 410 411 func TestHTTP_JobUpdate(t *testing.T) { 412 t.Parallel() 413 httpTest(t, nil, func(s *TestAgent) { 414 // Create the job 415 job := MockJob() 416 args := api.JobRegisterRequest{ 417 Job: job, 418 WriteRequest: api.WriteRequest{ 419 Region: "global", 420 Namespace: api.DefaultNamespace, 421 }, 422 } 423 buf := encodeReq(args) 424 425 // Make the HTTP request 426 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf) 427 if err != nil { 428 t.Fatalf("err: %v", err) 429 } 430 respW := httptest.NewRecorder() 431 432 // Make the request 433 obj, err := s.Server.JobSpecificRequest(respW, req) 434 if err != nil { 435 t.Fatalf("err: %v", err) 436 } 437 438 // Check the response 439 dereg := obj.(structs.JobRegisterResponse) 440 if dereg.EvalID == "" { 441 t.Fatalf("bad: %v", dereg) 442 } 443 444 // Check for the index 445 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 446 t.Fatalf("missing index") 447 } 448 449 // Check the job is registered 450 getReq := structs.JobSpecificRequest{ 451 JobID: *job.ID, 452 QueryOptions: structs.QueryOptions{ 453 Region: "global", 454 Namespace: structs.DefaultNamespace, 455 }, 456 } 457 var getResp structs.SingleJobResponse 458 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 459 t.Fatalf("err: %v", err) 460 } 461 462 if getResp.Job == nil { 463 t.Fatalf("job does not exist") 464 } 465 }) 466 } 467 468 func TestHTTP_JobUpdateRegion(t *testing.T) { 469 t.Parallel() 470 471 cases := []struct { 472 Name string 473 ConfigRegion string 474 APIRegion string 475 ExpectedRegion string 476 }{ 477 { 478 Name: "api region takes precedence", 479 ConfigRegion: "not-global", 480 APIRegion: "north-america", 481 ExpectedRegion: "north-america", 482 }, 483 { 484 Name: "config region is set", 485 ConfigRegion: "north-america", 486 APIRegion: "", 487 ExpectedRegion: "north-america", 488 }, 489 { 490 Name: "api region is set", 491 ConfigRegion: "", 492 APIRegion: "north-america", 493 ExpectedRegion: "north-america", 494 }, 495 { 496 Name: "defaults to node region global if no region is provided", 497 ConfigRegion: "", 498 APIRegion: "", 499 ExpectedRegion: "global", 500 }, 501 { 502 Name: "defaults to node region not-global if no region is provided", 503 ConfigRegion: "", 504 APIRegion: "", 505 ExpectedRegion: "not-global", 506 }, 507 } 508 509 for _, tc := range cases { 510 t.Run(tc.Name, func(t *testing.T) { 511 httpTest(t, func(c *Config) { c.Region = tc.ExpectedRegion }, func(s *TestAgent) { 512 // Create the job 513 job := MockRegionalJob() 514 515 if tc.ConfigRegion == "" { 516 job.Region = nil 517 } else { 518 job.Region = &tc.ConfigRegion 519 } 520 521 args := api.JobRegisterRequest{ 522 Job: job, 523 WriteRequest: api.WriteRequest{ 524 Namespace: api.DefaultNamespace, 525 Region: tc.APIRegion, 526 }, 527 } 528 529 buf := encodeReq(args) 530 531 // Make the HTTP request 532 url := "/v1/job/" + *job.ID 533 534 req, err := http.NewRequest("PUT", url, buf) 535 require.NoError(t, err) 536 respW := httptest.NewRecorder() 537 538 // Make the request 539 obj, err := s.Server.JobSpecificRequest(respW, req) 540 require.NoError(t, err) 541 542 // Check the response 543 dereg := obj.(structs.JobRegisterResponse) 544 require.NotEmpty(t, dereg.EvalID) 545 546 // Check for the index 547 require.NotEmpty(t, respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 548 549 // Check the job is registered 550 getReq := structs.JobSpecificRequest{ 551 JobID: *job.ID, 552 QueryOptions: structs.QueryOptions{ 553 Region: tc.ExpectedRegion, 554 Namespace: structs.DefaultNamespace, 555 }, 556 } 557 var getResp structs.SingleJobResponse 558 err = s.Agent.RPC("Job.GetJob", &getReq, &getResp) 559 require.NoError(t, err) 560 require.NotNil(t, getResp.Job, "job does not exist") 561 require.Equal(t, tc.ExpectedRegion, getResp.Job.Region) 562 }) 563 }) 564 } 565 } 566 567 func TestHTTP_JobDelete(t *testing.T) { 568 t.Parallel() 569 httpTest(t, nil, func(s *TestAgent) { 570 // Create the job 571 job := mock.Job() 572 args := structs.JobRegisterRequest{ 573 Job: job, 574 WriteRequest: structs.WriteRequest{ 575 Region: "global", 576 Namespace: structs.DefaultNamespace, 577 }, 578 } 579 var resp structs.JobRegisterResponse 580 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 581 t.Fatalf("err: %v", err) 582 } 583 584 // Make the HTTP request to do a soft delete 585 req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil) 586 if err != nil { 587 t.Fatalf("err: %v", err) 588 } 589 respW := httptest.NewRecorder() 590 591 // Make the request 592 obj, err := s.Server.JobSpecificRequest(respW, req) 593 if err != nil { 594 t.Fatalf("err: %v", err) 595 } 596 597 // Check the response 598 dereg := obj.(structs.JobDeregisterResponse) 599 if dereg.EvalID == "" { 600 t.Fatalf("bad: %v", dereg) 601 } 602 603 // Check for the index 604 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 605 t.Fatalf("missing index") 606 } 607 608 // Check the job is still queryable 609 getReq1 := structs.JobSpecificRequest{ 610 JobID: job.ID, 611 QueryOptions: structs.QueryOptions{ 612 Region: "global", 613 Namespace: structs.DefaultNamespace, 614 }, 615 } 616 var getResp1 structs.SingleJobResponse 617 if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil { 618 t.Fatalf("err: %v", err) 619 } 620 if getResp1.Job == nil { 621 t.Fatalf("job doesn't exists") 622 } 623 if !getResp1.Job.Stop { 624 t.Fatalf("job should be marked as stop") 625 } 626 627 // Make the HTTP request to do a purge delete 628 req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil) 629 if err != nil { 630 t.Fatalf("err: %v", err) 631 } 632 respW.Flush() 633 634 // Make the request 635 obj, err = s.Server.JobSpecificRequest(respW, req2) 636 if err != nil { 637 t.Fatalf("err: %v", err) 638 } 639 640 // Check the response 641 dereg = obj.(structs.JobDeregisterResponse) 642 if dereg.EvalID == "" { 643 t.Fatalf("bad: %v", dereg) 644 } 645 646 // Check for the index 647 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 648 t.Fatalf("missing index") 649 } 650 651 // Check the job is gone 652 getReq2 := structs.JobSpecificRequest{ 653 JobID: job.ID, 654 QueryOptions: structs.QueryOptions{ 655 Region: "global", 656 Namespace: structs.DefaultNamespace, 657 }, 658 } 659 var getResp2 structs.SingleJobResponse 660 if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil { 661 t.Fatalf("err: %v", err) 662 } 663 if getResp2.Job != nil { 664 t.Fatalf("job still exists") 665 } 666 }) 667 } 668 669 func TestHTTP_Job_ScaleTaskGroup(t *testing.T) { 670 t.Parallel() 671 672 require := require.New(t) 673 674 httpTest(t, nil, func(s *TestAgent) { 675 // Create the job 676 job := mock.Job() 677 args := structs.JobRegisterRequest{ 678 Job: job, 679 WriteRequest: structs.WriteRequest{ 680 Region: "global", 681 Namespace: structs.DefaultNamespace, 682 }, 683 } 684 var resp structs.JobRegisterResponse 685 require.NoError(s.Agent.RPC("Job.Register", &args, &resp)) 686 687 newCount := job.TaskGroups[0].Count + 1 688 scaleReq := &api.ScalingRequest{ 689 Count: helper.Int64ToPtr(int64(newCount)), 690 Message: "testing", 691 Target: map[string]string{ 692 "Job": job.ID, 693 "Group": job.TaskGroups[0].Name, 694 }, 695 } 696 buf := encodeReq(scaleReq) 697 698 // Make the HTTP request to scale the job group 699 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/scale", buf) 700 require.NoError(err) 701 respW := httptest.NewRecorder() 702 703 // Make the request 704 obj, err := s.Server.JobSpecificRequest(respW, req) 705 require.NoError(err) 706 707 // Check the response 708 resp = obj.(structs.JobRegisterResponse) 709 require.NotEmpty(resp.EvalID) 710 711 // Check for the index 712 require.NotEmpty(respW.Header().Get("X-Nomad-Index")) 713 714 // Check that the group count was changed 715 getReq := structs.JobSpecificRequest{ 716 JobID: job.ID, 717 QueryOptions: structs.QueryOptions{ 718 Region: "global", 719 Namespace: structs.DefaultNamespace, 720 }, 721 } 722 var getResp structs.SingleJobResponse 723 err = s.Agent.RPC("Job.GetJob", &getReq, &getResp) 724 require.NoError(err) 725 require.NotNil(getResp.Job) 726 require.Equal(newCount, getResp.Job.TaskGroups[0].Count) 727 }) 728 } 729 730 func TestHTTP_Job_ScaleStatus(t *testing.T) { 731 t.Parallel() 732 733 require := require.New(t) 734 735 httpTest(t, nil, func(s *TestAgent) { 736 // Create the job 737 job := mock.Job() 738 args := structs.JobRegisterRequest{ 739 Job: job, 740 WriteRequest: structs.WriteRequest{ 741 Region: "global", 742 Namespace: structs.DefaultNamespace, 743 }, 744 } 745 var resp structs.JobRegisterResponse 746 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 747 t.Fatalf("err: %v", err) 748 } 749 750 // Make the HTTP request to scale the job group 751 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/scale", nil) 752 require.NoError(err) 753 respW := httptest.NewRecorder() 754 755 // Make the request 756 obj, err := s.Server.JobSpecificRequest(respW, req) 757 require.NoError(err) 758 759 // Check the response 760 status := obj.(*structs.JobScaleStatus) 761 require.NotEmpty(resp.EvalID) 762 require.Equal(job.TaskGroups[0].Count, status.TaskGroups[job.TaskGroups[0].Name].Desired) 763 764 // Check for the index 765 require.NotEmpty(respW.Header().Get("X-Nomad-Index")) 766 }) 767 } 768 769 func TestHTTP_JobForceEvaluate(t *testing.T) { 770 t.Parallel() 771 httpTest(t, nil, func(s *TestAgent) { 772 // Create the job 773 job := mock.Job() 774 args := structs.JobRegisterRequest{ 775 Job: job, 776 WriteRequest: structs.WriteRequest{ 777 Region: "global", 778 Namespace: structs.DefaultNamespace, 779 }, 780 } 781 var resp structs.JobRegisterResponse 782 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 783 t.Fatalf("err: %v", err) 784 } 785 786 // Make the HTTP request 787 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil) 788 if err != nil { 789 t.Fatalf("err: %v", err) 790 } 791 respW := httptest.NewRecorder() 792 793 // Make the request 794 obj, err := s.Server.JobSpecificRequest(respW, req) 795 if err != nil { 796 t.Fatalf("err: %v", err) 797 } 798 799 // Check the response 800 reg := obj.(structs.JobRegisterResponse) 801 if reg.EvalID == "" { 802 t.Fatalf("bad: %v", reg) 803 } 804 805 // Check for the index 806 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 807 t.Fatalf("missing index") 808 } 809 }) 810 } 811 812 func TestHTTP_JobEvaluate_ForceReschedule(t *testing.T) { 813 t.Parallel() 814 httpTest(t, nil, func(s *TestAgent) { 815 // Create the job 816 job := mock.Job() 817 args := structs.JobRegisterRequest{ 818 Job: job, 819 WriteRequest: structs.WriteRequest{ 820 Region: "global", 821 Namespace: structs.DefaultNamespace, 822 }, 823 } 824 var resp structs.JobRegisterResponse 825 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 826 t.Fatalf("err: %v", err) 827 } 828 jobEvalReq := api.JobEvaluateRequest{ 829 JobID: job.ID, 830 EvalOptions: api.EvalOptions{ 831 ForceReschedule: true, 832 }, 833 } 834 835 buf := encodeReq(jobEvalReq) 836 837 // Make the HTTP request 838 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", buf) 839 if err != nil { 840 t.Fatalf("err: %v", err) 841 } 842 respW := httptest.NewRecorder() 843 844 // Make the request 845 obj, err := s.Server.JobSpecificRequest(respW, req) 846 if err != nil { 847 t.Fatalf("err: %v", err) 848 } 849 850 // Check the response 851 reg := obj.(structs.JobRegisterResponse) 852 if reg.EvalID == "" { 853 t.Fatalf("bad: %v", reg) 854 } 855 856 // Check for the index 857 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 858 t.Fatalf("missing index") 859 } 860 }) 861 } 862 863 func TestHTTP_JobEvaluations(t *testing.T) { 864 t.Parallel() 865 httpTest(t, nil, func(s *TestAgent) { 866 // Create the job 867 job := mock.Job() 868 args := structs.JobRegisterRequest{ 869 Job: job, 870 WriteRequest: structs.WriteRequest{ 871 Region: "global", 872 Namespace: structs.DefaultNamespace, 873 }, 874 } 875 var resp structs.JobRegisterResponse 876 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 877 t.Fatalf("err: %v", err) 878 } 879 880 // Make the HTTP request 881 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil) 882 if err != nil { 883 t.Fatalf("err: %v", err) 884 } 885 respW := httptest.NewRecorder() 886 887 // Make the request 888 obj, err := s.Server.JobSpecificRequest(respW, req) 889 if err != nil { 890 t.Fatalf("err: %v", err) 891 } 892 893 // Check the response 894 evals := obj.([]*structs.Evaluation) 895 // Can be multiple evals, use the last one, since they are in order 896 idx := len(evals) - 1 897 if len(evals) < 0 || evals[idx].ID != resp.EvalID { 898 t.Fatalf("bad: %v", evals) 899 } 900 901 // Check for the index 902 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 903 t.Fatalf("missing index") 904 } 905 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 906 t.Fatalf("missing known leader") 907 } 908 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 909 t.Fatalf("missing last contact") 910 } 911 }) 912 } 913 914 func TestHTTP_JobAllocations(t *testing.T) { 915 t.Parallel() 916 httpTest(t, nil, func(s *TestAgent) { 917 // Create the job 918 alloc1 := mock.Alloc() 919 args := structs.JobRegisterRequest{ 920 Job: alloc1.Job, 921 WriteRequest: structs.WriteRequest{ 922 Region: "global", 923 Namespace: structs.DefaultNamespace, 924 }, 925 } 926 var resp structs.JobRegisterResponse 927 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 928 t.Fatalf("err: %v", err) 929 } 930 931 // Directly manipulate the state 932 expectedDisplayMsg := "test message" 933 testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg) 934 var events []*structs.TaskEvent 935 events = append(events, testEvent) 936 taskState := &structs.TaskState{Events: events} 937 alloc1.TaskStates = make(map[string]*structs.TaskState) 938 alloc1.TaskStates["test"] = taskState 939 state := s.Agent.server.State() 940 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 941 if err != nil { 942 t.Fatalf("err: %v", err) 943 } 944 945 // Make the HTTP request 946 req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil) 947 if err != nil { 948 t.Fatalf("err: %v", err) 949 } 950 respW := httptest.NewRecorder() 951 952 // Make the request 953 obj, err := s.Server.JobSpecificRequest(respW, req) 954 if err != nil { 955 t.Fatalf("err: %v", err) 956 } 957 958 // Check the response 959 allocs := obj.([]*structs.AllocListStub) 960 if len(allocs) != 1 && allocs[0].ID != alloc1.ID { 961 t.Fatalf("bad: %v", allocs) 962 } 963 displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage 964 assert.Equal(t, expectedDisplayMsg, displayMsg) 965 966 // Check for the index 967 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 968 t.Fatalf("missing index") 969 } 970 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 971 t.Fatalf("missing known leader") 972 } 973 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 974 t.Fatalf("missing last contact") 975 } 976 }) 977 } 978 979 func TestHTTP_JobDeployments(t *testing.T) { 980 assert := assert.New(t) 981 t.Parallel() 982 httpTest(t, nil, func(s *TestAgent) { 983 // Create the job 984 j := mock.Job() 985 args := structs.JobRegisterRequest{ 986 Job: j, 987 WriteRequest: structs.WriteRequest{ 988 Region: "global", 989 Namespace: structs.DefaultNamespace, 990 }, 991 } 992 var resp structs.JobRegisterResponse 993 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 994 995 // Directly manipulate the state 996 state := s.Agent.server.State() 997 d := mock.Deployment() 998 d.JobID = j.ID 999 d.JobCreateIndex = resp.JobModifyIndex 1000 1001 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 1002 1003 // Make the HTTP request 1004 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil) 1005 assert.Nil(err, "HTTP") 1006 respW := httptest.NewRecorder() 1007 1008 // Make the request 1009 obj, err := s.Server.JobSpecificRequest(respW, req) 1010 assert.Nil(err, "JobSpecificRequest") 1011 1012 // Check the response 1013 deploys := obj.([]*structs.Deployment) 1014 assert.Len(deploys, 1, "deployments") 1015 assert.Equal(d.ID, deploys[0].ID, "deployment id") 1016 1017 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 1018 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 1019 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 1020 }) 1021 } 1022 1023 func TestHTTP_JobDeployment(t *testing.T) { 1024 assert := assert.New(t) 1025 t.Parallel() 1026 httpTest(t, nil, func(s *TestAgent) { 1027 // Create the job 1028 j := mock.Job() 1029 args := structs.JobRegisterRequest{ 1030 Job: j, 1031 WriteRequest: structs.WriteRequest{ 1032 Region: "global", 1033 Namespace: structs.DefaultNamespace, 1034 }, 1035 } 1036 var resp structs.JobRegisterResponse 1037 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 1038 1039 // Directly manipulate the state 1040 state := s.Agent.server.State() 1041 d := mock.Deployment() 1042 d.JobID = j.ID 1043 d.JobCreateIndex = resp.JobModifyIndex 1044 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 1045 1046 // Make the HTTP request 1047 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil) 1048 assert.Nil(err, "HTTP") 1049 respW := httptest.NewRecorder() 1050 1051 // Make the request 1052 obj, err := s.Server.JobSpecificRequest(respW, req) 1053 assert.Nil(err, "JobSpecificRequest") 1054 1055 // Check the response 1056 out := obj.(*structs.Deployment) 1057 assert.NotNil(out, "deployment") 1058 assert.Equal(d.ID, out.ID, "deployment id") 1059 1060 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 1061 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 1062 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 1063 }) 1064 } 1065 1066 func TestHTTP_JobVersions(t *testing.T) { 1067 t.Parallel() 1068 httpTest(t, nil, func(s *TestAgent) { 1069 // Create the job 1070 job := mock.Job() 1071 args := structs.JobRegisterRequest{ 1072 Job: job, 1073 WriteRequest: structs.WriteRequest{ 1074 Region: "global", 1075 Namespace: structs.DefaultNamespace, 1076 }, 1077 } 1078 var resp structs.JobRegisterResponse 1079 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 1080 t.Fatalf("err: %v", err) 1081 } 1082 1083 job2 := mock.Job() 1084 job2.ID = job.ID 1085 job2.Priority = 100 1086 1087 args2 := structs.JobRegisterRequest{ 1088 Job: job2, 1089 WriteRequest: structs.WriteRequest{ 1090 Region: "global", 1091 Namespace: structs.DefaultNamespace, 1092 }, 1093 } 1094 var resp2 structs.JobRegisterResponse 1095 if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil { 1096 t.Fatalf("err: %v", err) 1097 } 1098 1099 // Make the HTTP request 1100 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil) 1101 if err != nil { 1102 t.Fatalf("err: %v", err) 1103 } 1104 respW := httptest.NewRecorder() 1105 1106 // Make the request 1107 obj, err := s.Server.JobSpecificRequest(respW, req) 1108 if err != nil { 1109 t.Fatalf("err: %v", err) 1110 } 1111 1112 // Check the response 1113 vResp := obj.(structs.JobVersionsResponse) 1114 versions := vResp.Versions 1115 if len(versions) != 2 { 1116 t.Fatalf("got %d versions; want 2", len(versions)) 1117 } 1118 1119 if v := versions[0]; v.Version != 1 || v.Priority != 100 { 1120 t.Fatalf("bad %v", v) 1121 } 1122 1123 if v := versions[1]; v.Version != 0 { 1124 t.Fatalf("bad %v", v) 1125 } 1126 1127 if len(vResp.Diffs) != 1 { 1128 t.Fatalf("bad %v", vResp) 1129 } 1130 1131 // Check for the index 1132 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1133 t.Fatalf("missing index") 1134 } 1135 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 1136 t.Fatalf("missing known leader") 1137 } 1138 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 1139 t.Fatalf("missing last contact") 1140 } 1141 }) 1142 } 1143 1144 func TestHTTP_PeriodicForce(t *testing.T) { 1145 t.Parallel() 1146 httpTest(t, nil, func(s *TestAgent) { 1147 // Create and register a periodic job. 1148 job := mock.PeriodicJob() 1149 args := structs.JobRegisterRequest{ 1150 Job: job, 1151 WriteRequest: structs.WriteRequest{ 1152 Region: "global", 1153 Namespace: structs.DefaultNamespace, 1154 }, 1155 } 1156 var resp structs.JobRegisterResponse 1157 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 1158 t.Fatalf("err: %v", err) 1159 } 1160 1161 // Make the HTTP request 1162 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) 1163 if err != nil { 1164 t.Fatalf("err: %v", err) 1165 } 1166 respW := httptest.NewRecorder() 1167 1168 // Make the request 1169 obj, err := s.Server.JobSpecificRequest(respW, req) 1170 if err != nil { 1171 t.Fatalf("err: %v", err) 1172 } 1173 1174 // Check for the index 1175 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1176 t.Fatalf("missing index") 1177 } 1178 1179 // Check the response 1180 r := obj.(structs.PeriodicForceResponse) 1181 if r.EvalID == "" { 1182 t.Fatalf("bad: %#v", r) 1183 } 1184 }) 1185 } 1186 1187 func TestHTTP_JobPlan(t *testing.T) { 1188 t.Parallel() 1189 httpTest(t, nil, func(s *TestAgent) { 1190 // Create the job 1191 job := MockJob() 1192 args := api.JobPlanRequest{ 1193 Job: job, 1194 Diff: true, 1195 WriteRequest: api.WriteRequest{ 1196 Region: "global", 1197 Namespace: api.DefaultNamespace, 1198 }, 1199 } 1200 buf := encodeReq(args) 1201 1202 // Make the HTTP request 1203 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) 1204 if err != nil { 1205 t.Fatalf("err: %v", err) 1206 } 1207 respW := httptest.NewRecorder() 1208 1209 // Make the request 1210 obj, err := s.Server.JobSpecificRequest(respW, req) 1211 if err != nil { 1212 t.Fatalf("err: %v", err) 1213 } 1214 1215 // Check the response 1216 plan := obj.(structs.JobPlanResponse) 1217 if plan.Annotations == nil { 1218 t.Fatalf("bad: %v", plan) 1219 } 1220 1221 if plan.Diff == nil { 1222 t.Fatalf("bad: %v", plan) 1223 } 1224 }) 1225 } 1226 1227 func TestHTTP_JobPlanRegion(t *testing.T) { 1228 t.Parallel() 1229 1230 cases := []struct { 1231 Name string 1232 ConfigRegion string 1233 APIRegion string 1234 ExpectedRegion string 1235 }{ 1236 { 1237 Name: "api region takes precedence", 1238 ConfigRegion: "not-global", 1239 APIRegion: "north-america", 1240 ExpectedRegion: "north-america", 1241 }, 1242 { 1243 Name: "config region is set", 1244 ConfigRegion: "north-america", 1245 APIRegion: "", 1246 ExpectedRegion: "north-america", 1247 }, 1248 { 1249 Name: "api region is set", 1250 ConfigRegion: "", 1251 APIRegion: "north-america", 1252 ExpectedRegion: "north-america", 1253 }, 1254 { 1255 Name: "falls back to default if no region is provided", 1256 ConfigRegion: "", 1257 APIRegion: "", 1258 ExpectedRegion: "global", 1259 }, 1260 } 1261 1262 for _, tc := range cases { 1263 t.Run(tc.Name, func(t *testing.T) { 1264 httpTest(t, func(c *Config) { c.Region = tc.ExpectedRegion }, func(s *TestAgent) { 1265 // Create the job 1266 job := MockRegionalJob() 1267 1268 if tc.ConfigRegion == "" { 1269 job.Region = nil 1270 } else { 1271 job.Region = &tc.ConfigRegion 1272 } 1273 1274 args := api.JobPlanRequest{ 1275 Job: job, 1276 Diff: true, 1277 WriteRequest: api.WriteRequest{ 1278 Region: tc.APIRegion, 1279 Namespace: api.DefaultNamespace, 1280 }, 1281 } 1282 buf := encodeReq(args) 1283 1284 // Make the HTTP request 1285 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) 1286 require.NoError(t, err) 1287 respW := httptest.NewRecorder() 1288 1289 // Make the request 1290 obj, err := s.Server.JobSpecificRequest(respW, req) 1291 require.NoError(t, err) 1292 1293 // Check the response 1294 plan := obj.(structs.JobPlanResponse) 1295 require.NotNil(t, plan.Annotations) 1296 require.NotNil(t, plan.Diff) 1297 }) 1298 }) 1299 } 1300 } 1301 1302 func TestHTTP_JobDispatch(t *testing.T) { 1303 t.Parallel() 1304 httpTest(t, nil, func(s *TestAgent) { 1305 // Create the parameterized job 1306 job := mock.BatchJob() 1307 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1308 1309 args := structs.JobRegisterRequest{ 1310 Job: job, 1311 WriteRequest: structs.WriteRequest{ 1312 Region: "global", 1313 Namespace: structs.DefaultNamespace, 1314 }, 1315 } 1316 var resp structs.JobRegisterResponse 1317 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 1318 t.Fatalf("err: %v", err) 1319 } 1320 1321 // Make the request 1322 respW := httptest.NewRecorder() 1323 args2 := structs.JobDispatchRequest{ 1324 WriteRequest: structs.WriteRequest{ 1325 Region: "global", 1326 Namespace: structs.DefaultNamespace, 1327 }, 1328 } 1329 buf := encodeReq(args2) 1330 1331 // Make the HTTP request 1332 req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) 1333 if err != nil { 1334 t.Fatalf("err: %v", err) 1335 } 1336 respW.Flush() 1337 1338 // Make the request 1339 obj, err := s.Server.JobSpecificRequest(respW, req2) 1340 if err != nil { 1341 t.Fatalf("err: %v", err) 1342 } 1343 1344 // Check the response 1345 dispatch := obj.(structs.JobDispatchResponse) 1346 if dispatch.EvalID == "" { 1347 t.Fatalf("bad: %v", dispatch) 1348 } 1349 1350 if dispatch.DispatchedJobID == "" { 1351 t.Fatalf("bad: %v", dispatch) 1352 } 1353 }) 1354 } 1355 1356 func TestHTTP_JobRevert(t *testing.T) { 1357 t.Parallel() 1358 httpTest(t, nil, func(s *TestAgent) { 1359 // Create the job and register it twice 1360 job := mock.Job() 1361 regReq := structs.JobRegisterRequest{ 1362 Job: job, 1363 WriteRequest: structs.WriteRequest{ 1364 Region: "global", 1365 Namespace: structs.DefaultNamespace, 1366 }, 1367 } 1368 var regResp structs.JobRegisterResponse 1369 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1370 t.Fatalf("err: %v", err) 1371 } 1372 1373 // Change the job to get a new version 1374 job.Datacenters = append(job.Datacenters, "foo") 1375 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1376 t.Fatalf("err: %v", err) 1377 } 1378 1379 args := structs.JobRevertRequest{ 1380 JobID: job.ID, 1381 JobVersion: 0, 1382 WriteRequest: structs.WriteRequest{ 1383 Region: "global", 1384 Namespace: structs.DefaultNamespace, 1385 }, 1386 } 1387 buf := encodeReq(args) 1388 1389 // Make the HTTP request 1390 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) 1391 if err != nil { 1392 t.Fatalf("err: %v", err) 1393 } 1394 respW := httptest.NewRecorder() 1395 1396 // Make the request 1397 obj, err := s.Server.JobSpecificRequest(respW, req) 1398 if err != nil { 1399 t.Fatalf("err: %v", err) 1400 } 1401 1402 // Check the response 1403 revertResp := obj.(structs.JobRegisterResponse) 1404 if revertResp.EvalID == "" { 1405 t.Fatalf("bad: %v", revertResp) 1406 } 1407 1408 // Check for the index 1409 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1410 t.Fatalf("missing index") 1411 } 1412 }) 1413 } 1414 1415 func TestHTTP_JobStable(t *testing.T) { 1416 t.Parallel() 1417 httpTest(t, nil, func(s *TestAgent) { 1418 // Create the job and register it twice 1419 job := mock.Job() 1420 regReq := structs.JobRegisterRequest{ 1421 Job: job, 1422 WriteRequest: structs.WriteRequest{ 1423 Region: "global", 1424 Namespace: structs.DefaultNamespace, 1425 }, 1426 } 1427 var regResp structs.JobRegisterResponse 1428 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1429 t.Fatalf("err: %v", err) 1430 } 1431 1432 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1433 t.Fatalf("err: %v", err) 1434 } 1435 1436 args := structs.JobStabilityRequest{ 1437 JobID: job.ID, 1438 JobVersion: 0, 1439 Stable: true, 1440 WriteRequest: structs.WriteRequest{ 1441 Region: "global", 1442 Namespace: structs.DefaultNamespace, 1443 }, 1444 } 1445 buf := encodeReq(args) 1446 1447 // Make the HTTP request 1448 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf) 1449 if err != nil { 1450 t.Fatalf("err: %v", err) 1451 } 1452 respW := httptest.NewRecorder() 1453 1454 // Make the request 1455 obj, err := s.Server.JobSpecificRequest(respW, req) 1456 if err != nil { 1457 t.Fatalf("err: %v", err) 1458 } 1459 1460 // Check the response 1461 stableResp := obj.(structs.JobStabilityResponse) 1462 if stableResp.Index == 0 { 1463 t.Fatalf("bad: %v", stableResp) 1464 } 1465 1466 // Check for the index 1467 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1468 t.Fatalf("missing index") 1469 } 1470 }) 1471 } 1472 1473 func TestJobs_ApiJobToStructsJob(t *testing.T) { 1474 apiJob := &api.Job{ 1475 Stop: helper.BoolToPtr(true), 1476 Region: helper.StringToPtr("global"), 1477 Namespace: helper.StringToPtr("foo"), 1478 ID: helper.StringToPtr("foo"), 1479 ParentID: helper.StringToPtr("lol"), 1480 Name: helper.StringToPtr("name"), 1481 Type: helper.StringToPtr("service"), 1482 Priority: helper.IntToPtr(50), 1483 AllAtOnce: helper.BoolToPtr(true), 1484 Datacenters: []string{"dc1", "dc2"}, 1485 Constraints: []*api.Constraint{ 1486 { 1487 LTarget: "a", 1488 RTarget: "b", 1489 Operand: "c", 1490 }, 1491 }, 1492 Affinities: []*api.Affinity{ 1493 { 1494 LTarget: "a", 1495 RTarget: "b", 1496 Operand: "c", 1497 Weight: helper.Int8ToPtr(50), 1498 }, 1499 }, 1500 Update: &api.UpdateStrategy{ 1501 Stagger: helper.TimeToPtr(1 * time.Second), 1502 MaxParallel: helper.IntToPtr(5), 1503 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 1504 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 1505 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 1506 ProgressDeadline: helper.TimeToPtr(3 * time.Minute), 1507 AutoRevert: helper.BoolToPtr(false), 1508 Canary: helper.IntToPtr(1), 1509 }, 1510 Spreads: []*api.Spread{ 1511 { 1512 Attribute: "${meta.rack}", 1513 Weight: helper.Int8ToPtr(100), 1514 SpreadTarget: []*api.SpreadTarget{ 1515 { 1516 Value: "r1", 1517 Percent: 50, 1518 }, 1519 }, 1520 }, 1521 }, 1522 Periodic: &api.PeriodicConfig{ 1523 Enabled: helper.BoolToPtr(true), 1524 Spec: helper.StringToPtr("spec"), 1525 SpecType: helper.StringToPtr("cron"), 1526 ProhibitOverlap: helper.BoolToPtr(true), 1527 TimeZone: helper.StringToPtr("test zone"), 1528 }, 1529 ParameterizedJob: &api.ParameterizedJobConfig{ 1530 Payload: "payload", 1531 MetaRequired: []string{"a", "b"}, 1532 MetaOptional: []string{"c", "d"}, 1533 }, 1534 Payload: []byte("payload"), 1535 Meta: map[string]string{ 1536 "foo": "bar", 1537 }, 1538 TaskGroups: []*api.TaskGroup{ 1539 { 1540 Name: helper.StringToPtr("group1"), 1541 Count: helper.IntToPtr(5), 1542 Constraints: []*api.Constraint{ 1543 { 1544 LTarget: "x", 1545 RTarget: "y", 1546 Operand: "z", 1547 }, 1548 }, 1549 Affinities: []*api.Affinity{ 1550 { 1551 LTarget: "x", 1552 RTarget: "y", 1553 Operand: "z", 1554 Weight: helper.Int8ToPtr(100), 1555 }, 1556 }, 1557 RestartPolicy: &api.RestartPolicy{ 1558 Interval: helper.TimeToPtr(1 * time.Second), 1559 Attempts: helper.IntToPtr(5), 1560 Delay: helper.TimeToPtr(10 * time.Second), 1561 Mode: helper.StringToPtr("delay"), 1562 }, 1563 ReschedulePolicy: &api.ReschedulePolicy{ 1564 Interval: helper.TimeToPtr(12 * time.Hour), 1565 Attempts: helper.IntToPtr(5), 1566 DelayFunction: helper.StringToPtr("constant"), 1567 Delay: helper.TimeToPtr(30 * time.Second), 1568 Unlimited: helper.BoolToPtr(true), 1569 MaxDelay: helper.TimeToPtr(20 * time.Minute), 1570 }, 1571 Migrate: &api.MigrateStrategy{ 1572 MaxParallel: helper.IntToPtr(12), 1573 HealthCheck: helper.StringToPtr("task_events"), 1574 MinHealthyTime: helper.TimeToPtr(12 * time.Hour), 1575 HealthyDeadline: helper.TimeToPtr(12 * time.Hour), 1576 }, 1577 Spreads: []*api.Spread{ 1578 { 1579 Attribute: "${node.datacenter}", 1580 Weight: helper.Int8ToPtr(100), 1581 SpreadTarget: []*api.SpreadTarget{ 1582 { 1583 Value: "dc1", 1584 Percent: 100, 1585 }, 1586 }, 1587 }, 1588 }, 1589 EphemeralDisk: &api.EphemeralDisk{ 1590 SizeMB: helper.IntToPtr(100), 1591 Sticky: helper.BoolToPtr(true), 1592 Migrate: helper.BoolToPtr(true), 1593 }, 1594 Update: &api.UpdateStrategy{ 1595 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), 1596 MinHealthyTime: helper.TimeToPtr(2 * time.Minute), 1597 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 1598 ProgressDeadline: helper.TimeToPtr(5 * time.Minute), 1599 AutoRevert: helper.BoolToPtr(true), 1600 }, 1601 Meta: map[string]string{ 1602 "key": "value", 1603 }, 1604 Services: []*api.Service{ 1605 { 1606 Name: "groupserviceA", 1607 Tags: []string{"a", "b"}, 1608 CanaryTags: []string{"d", "e"}, 1609 EnableTagOverride: true, 1610 PortLabel: "1234", 1611 Meta: map[string]string{ 1612 "servicemeta": "foobar", 1613 }, 1614 CheckRestart: &api.CheckRestart{ 1615 Limit: 4, 1616 Grace: helper.TimeToPtr(11 * time.Second), 1617 }, 1618 Checks: []api.ServiceCheck{ 1619 { 1620 Id: "hello", 1621 Name: "bar", 1622 Type: "http", 1623 Command: "foo", 1624 Args: []string{"a", "b"}, 1625 Path: "/check", 1626 Protocol: "http", 1627 PortLabel: "foo", 1628 AddressMode: "driver", 1629 GRPCService: "foo.Bar", 1630 GRPCUseTLS: true, 1631 Interval: 4 * time.Second, 1632 Timeout: 2 * time.Second, 1633 InitialStatus: "ok", 1634 CheckRestart: &api.CheckRestart{ 1635 Limit: 3, 1636 IgnoreWarnings: true, 1637 }, 1638 TaskName: "task1", 1639 }, 1640 }, 1641 Connect: &api.ConsulConnect{ 1642 Native: false, 1643 SidecarService: &api.ConsulSidecarService{ 1644 Tags: []string{"f", "g"}, 1645 Port: "9000", 1646 }, 1647 }, 1648 }, 1649 }, 1650 Tasks: []*api.Task{ 1651 { 1652 Name: "task1", 1653 Leader: true, 1654 Driver: "docker", 1655 User: "mary", 1656 Config: map[string]interface{}{ 1657 "lol": "code", 1658 }, 1659 Env: map[string]string{ 1660 "hello": "world", 1661 }, 1662 Constraints: []*api.Constraint{ 1663 { 1664 LTarget: "x", 1665 RTarget: "y", 1666 Operand: "z", 1667 }, 1668 }, 1669 Affinities: []*api.Affinity{ 1670 { 1671 LTarget: "a", 1672 RTarget: "b", 1673 Operand: "c", 1674 Weight: helper.Int8ToPtr(50), 1675 }, 1676 }, 1677 RestartPolicy: &api.RestartPolicy{ 1678 Interval: helper.TimeToPtr(2 * time.Second), 1679 Attempts: helper.IntToPtr(10), 1680 Delay: helper.TimeToPtr(20 * time.Second), 1681 Mode: helper.StringToPtr("delay"), 1682 }, 1683 Services: []*api.Service{ 1684 { 1685 Id: "id", 1686 Name: "serviceA", 1687 Tags: []string{"1", "2"}, 1688 CanaryTags: []string{"3", "4"}, 1689 EnableTagOverride: true, 1690 PortLabel: "foo", 1691 Meta: map[string]string{ 1692 "servicemeta": "foobar", 1693 }, 1694 CheckRestart: &api.CheckRestart{ 1695 Limit: 4, 1696 Grace: helper.TimeToPtr(11 * time.Second), 1697 }, 1698 Checks: []api.ServiceCheck{ 1699 { 1700 Id: "hello", 1701 Name: "bar", 1702 Type: "http", 1703 Command: "foo", 1704 Args: []string{"a", "b"}, 1705 Path: "/check", 1706 Protocol: "http", 1707 PortLabel: "foo", 1708 AddressMode: "driver", 1709 GRPCService: "foo.Bar", 1710 GRPCUseTLS: true, 1711 Interval: 4 * time.Second, 1712 Timeout: 2 * time.Second, 1713 InitialStatus: "ok", 1714 CheckRestart: &api.CheckRestart{ 1715 Limit: 3, 1716 IgnoreWarnings: true, 1717 }, 1718 }, 1719 { 1720 Id: "check2id", 1721 Name: "check2", 1722 Type: "tcp", 1723 PortLabel: "foo", 1724 Interval: 4 * time.Second, 1725 Timeout: 2 * time.Second, 1726 }, 1727 }, 1728 }, 1729 }, 1730 Resources: &api.Resources{ 1731 CPU: helper.IntToPtr(100), 1732 MemoryMB: helper.IntToPtr(10), 1733 Networks: []*api.NetworkResource{ 1734 { 1735 IP: "10.10.11.1", 1736 MBits: helper.IntToPtr(10), 1737 ReservedPorts: []api.Port{ 1738 { 1739 Label: "http", 1740 Value: 80, 1741 }, 1742 }, 1743 DynamicPorts: []api.Port{ 1744 { 1745 Label: "ssh", 1746 Value: 2000, 1747 }, 1748 }, 1749 }, 1750 }, 1751 Devices: []*api.RequestedDevice{ 1752 { 1753 Name: "nvidia/gpu", 1754 Count: helper.Uint64ToPtr(4), 1755 Constraints: []*api.Constraint{ 1756 { 1757 LTarget: "x", 1758 RTarget: "y", 1759 Operand: "z", 1760 }, 1761 }, 1762 Affinities: []*api.Affinity{ 1763 { 1764 LTarget: "a", 1765 RTarget: "b", 1766 Operand: "c", 1767 Weight: helper.Int8ToPtr(50), 1768 }, 1769 }, 1770 }, 1771 { 1772 Name: "gpu", 1773 Count: nil, 1774 }, 1775 }, 1776 }, 1777 Meta: map[string]string{ 1778 "lol": "code", 1779 }, 1780 KillTimeout: helper.TimeToPtr(10 * time.Second), 1781 KillSignal: "SIGQUIT", 1782 LogConfig: &api.LogConfig{ 1783 MaxFiles: helper.IntToPtr(10), 1784 MaxFileSizeMB: helper.IntToPtr(100), 1785 }, 1786 Artifacts: []*api.TaskArtifact{ 1787 { 1788 GetterSource: helper.StringToPtr("source"), 1789 GetterOptions: map[string]string{ 1790 "a": "b", 1791 }, 1792 GetterMode: helper.StringToPtr("dir"), 1793 RelativeDest: helper.StringToPtr("dest"), 1794 }, 1795 }, 1796 Vault: &api.Vault{ 1797 Policies: []string{"a", "b", "c"}, 1798 Env: helper.BoolToPtr(true), 1799 ChangeMode: helper.StringToPtr("c"), 1800 ChangeSignal: helper.StringToPtr("sighup"), 1801 }, 1802 Templates: []*api.Template{ 1803 { 1804 SourcePath: helper.StringToPtr("source"), 1805 DestPath: helper.StringToPtr("dest"), 1806 EmbeddedTmpl: helper.StringToPtr("embedded"), 1807 ChangeMode: helper.StringToPtr("change"), 1808 ChangeSignal: helper.StringToPtr("signal"), 1809 Splay: helper.TimeToPtr(1 * time.Minute), 1810 Perms: helper.StringToPtr("666"), 1811 LeftDelim: helper.StringToPtr("abc"), 1812 RightDelim: helper.StringToPtr("def"), 1813 Envvars: helper.BoolToPtr(true), 1814 }, 1815 }, 1816 DispatchPayload: &api.DispatchPayloadConfig{ 1817 File: "fileA", 1818 }, 1819 }, 1820 }, 1821 }, 1822 }, 1823 ConsulToken: helper.StringToPtr("abc123"), 1824 VaultToken: helper.StringToPtr("def456"), 1825 Status: helper.StringToPtr("status"), 1826 StatusDescription: helper.StringToPtr("status_desc"), 1827 Version: helper.Uint64ToPtr(10), 1828 CreateIndex: helper.Uint64ToPtr(1), 1829 ModifyIndex: helper.Uint64ToPtr(3), 1830 JobModifyIndex: helper.Uint64ToPtr(5), 1831 } 1832 1833 expected := &structs.Job{ 1834 Stop: true, 1835 Region: "global", 1836 Namespace: "foo", 1837 ID: "foo", 1838 ParentID: "lol", 1839 Name: "name", 1840 Type: "service", 1841 Priority: 50, 1842 AllAtOnce: true, 1843 Datacenters: []string{"dc1", "dc2"}, 1844 Constraints: []*structs.Constraint{ 1845 { 1846 LTarget: "a", 1847 RTarget: "b", 1848 Operand: "c", 1849 }, 1850 }, 1851 Affinities: []*structs.Affinity{ 1852 { 1853 LTarget: "a", 1854 RTarget: "b", 1855 Operand: "c", 1856 Weight: 50, 1857 }, 1858 }, 1859 Spreads: []*structs.Spread{ 1860 { 1861 Attribute: "${meta.rack}", 1862 Weight: 100, 1863 SpreadTarget: []*structs.SpreadTarget{ 1864 { 1865 Value: "r1", 1866 Percent: 50, 1867 }, 1868 }, 1869 }, 1870 }, 1871 Update: structs.UpdateStrategy{ 1872 Stagger: 1 * time.Second, 1873 MaxParallel: 5, 1874 }, 1875 Periodic: &structs.PeriodicConfig{ 1876 Enabled: true, 1877 Spec: "spec", 1878 SpecType: "cron", 1879 ProhibitOverlap: true, 1880 TimeZone: "test zone", 1881 }, 1882 ParameterizedJob: &structs.ParameterizedJobConfig{ 1883 Payload: "payload", 1884 MetaRequired: []string{"a", "b"}, 1885 MetaOptional: []string{"c", "d"}, 1886 }, 1887 Payload: []byte("payload"), 1888 Meta: map[string]string{ 1889 "foo": "bar", 1890 }, 1891 TaskGroups: []*structs.TaskGroup{ 1892 { 1893 Name: "group1", 1894 Count: 5, 1895 Constraints: []*structs.Constraint{ 1896 { 1897 LTarget: "x", 1898 RTarget: "y", 1899 Operand: "z", 1900 }, 1901 }, 1902 Affinities: []*structs.Affinity{ 1903 { 1904 LTarget: "x", 1905 RTarget: "y", 1906 Operand: "z", 1907 Weight: 100, 1908 }, 1909 }, 1910 RestartPolicy: &structs.RestartPolicy{ 1911 Interval: 1 * time.Second, 1912 Attempts: 5, 1913 Delay: 10 * time.Second, 1914 Mode: "delay", 1915 }, 1916 Spreads: []*structs.Spread{ 1917 { 1918 Attribute: "${node.datacenter}", 1919 Weight: 100, 1920 SpreadTarget: []*structs.SpreadTarget{ 1921 { 1922 Value: "dc1", 1923 Percent: 100, 1924 }, 1925 }, 1926 }, 1927 }, 1928 ReschedulePolicy: &structs.ReschedulePolicy{ 1929 Interval: 12 * time.Hour, 1930 Attempts: 5, 1931 DelayFunction: "constant", 1932 Delay: 30 * time.Second, 1933 Unlimited: true, 1934 MaxDelay: 20 * time.Minute, 1935 }, 1936 Migrate: &structs.MigrateStrategy{ 1937 MaxParallel: 12, 1938 HealthCheck: "task_events", 1939 MinHealthyTime: 12 * time.Hour, 1940 HealthyDeadline: 12 * time.Hour, 1941 }, 1942 EphemeralDisk: &structs.EphemeralDisk{ 1943 SizeMB: 100, 1944 Sticky: true, 1945 Migrate: true, 1946 }, 1947 Update: &structs.UpdateStrategy{ 1948 Stagger: 1 * time.Second, 1949 MaxParallel: 5, 1950 HealthCheck: structs.UpdateStrategyHealthCheck_Checks, 1951 MinHealthyTime: 2 * time.Minute, 1952 HealthyDeadline: 5 * time.Minute, 1953 ProgressDeadline: 5 * time.Minute, 1954 AutoRevert: true, 1955 AutoPromote: false, 1956 Canary: 1, 1957 }, 1958 Meta: map[string]string{ 1959 "key": "value", 1960 }, 1961 Services: []*structs.Service{ 1962 { 1963 Name: "groupserviceA", 1964 Tags: []string{"a", "b"}, 1965 CanaryTags: []string{"d", "e"}, 1966 EnableTagOverride: true, 1967 PortLabel: "1234", 1968 AddressMode: "auto", 1969 Meta: map[string]string{ 1970 "servicemeta": "foobar", 1971 }, 1972 Checks: []*structs.ServiceCheck{ 1973 { 1974 Name: "bar", 1975 Type: "http", 1976 Command: "foo", 1977 Args: []string{"a", "b"}, 1978 Path: "/check", 1979 Protocol: "http", 1980 PortLabel: "foo", 1981 AddressMode: "driver", 1982 GRPCService: "foo.Bar", 1983 GRPCUseTLS: true, 1984 Interval: 4 * time.Second, 1985 Timeout: 2 * time.Second, 1986 InitialStatus: "ok", 1987 CheckRestart: &structs.CheckRestart{ 1988 Grace: 11 * time.Second, 1989 Limit: 3, 1990 IgnoreWarnings: true, 1991 }, 1992 TaskName: "task1", 1993 }, 1994 }, 1995 Connect: &structs.ConsulConnect{ 1996 Native: false, 1997 SidecarService: &structs.ConsulSidecarService{ 1998 Tags: []string{"f", "g"}, 1999 Port: "9000", 2000 }, 2001 }, 2002 }, 2003 }, 2004 Tasks: []*structs.Task{ 2005 { 2006 Name: "task1", 2007 Driver: "docker", 2008 Leader: true, 2009 User: "mary", 2010 Config: map[string]interface{}{ 2011 "lol": "code", 2012 }, 2013 Constraints: []*structs.Constraint{ 2014 { 2015 LTarget: "x", 2016 RTarget: "y", 2017 Operand: "z", 2018 }, 2019 }, 2020 Affinities: []*structs.Affinity{ 2021 { 2022 LTarget: "a", 2023 RTarget: "b", 2024 Operand: "c", 2025 Weight: 50, 2026 }, 2027 }, 2028 Env: map[string]string{ 2029 "hello": "world", 2030 }, 2031 RestartPolicy: &structs.RestartPolicy{ 2032 Interval: 2 * time.Second, 2033 Attempts: 10, 2034 Delay: 20 * time.Second, 2035 Mode: "delay", 2036 }, 2037 Services: []*structs.Service{ 2038 { 2039 Name: "serviceA", 2040 Tags: []string{"1", "2"}, 2041 CanaryTags: []string{"3", "4"}, 2042 EnableTagOverride: true, 2043 PortLabel: "foo", 2044 AddressMode: "auto", 2045 Meta: map[string]string{ 2046 "servicemeta": "foobar", 2047 }, 2048 Checks: []*structs.ServiceCheck{ 2049 { 2050 Name: "bar", 2051 Type: "http", 2052 Command: "foo", 2053 Args: []string{"a", "b"}, 2054 Path: "/check", 2055 Protocol: "http", 2056 PortLabel: "foo", 2057 AddressMode: "driver", 2058 Interval: 4 * time.Second, 2059 Timeout: 2 * time.Second, 2060 InitialStatus: "ok", 2061 GRPCService: "foo.Bar", 2062 GRPCUseTLS: true, 2063 CheckRestart: &structs.CheckRestart{ 2064 Limit: 3, 2065 Grace: 11 * time.Second, 2066 IgnoreWarnings: true, 2067 }, 2068 }, 2069 { 2070 Name: "check2", 2071 Type: "tcp", 2072 PortLabel: "foo", 2073 Interval: 4 * time.Second, 2074 Timeout: 2 * time.Second, 2075 CheckRestart: &structs.CheckRestart{ 2076 Limit: 4, 2077 Grace: 11 * time.Second, 2078 }, 2079 }, 2080 }, 2081 }, 2082 }, 2083 Resources: &structs.Resources{ 2084 CPU: 100, 2085 MemoryMB: 10, 2086 Networks: []*structs.NetworkResource{ 2087 { 2088 IP: "10.10.11.1", 2089 MBits: 10, 2090 ReservedPorts: []structs.Port{ 2091 { 2092 Label: "http", 2093 Value: 80, 2094 }, 2095 }, 2096 DynamicPorts: []structs.Port{ 2097 { 2098 Label: "ssh", 2099 Value: 2000, 2100 }, 2101 }, 2102 }, 2103 }, 2104 Devices: []*structs.RequestedDevice{ 2105 { 2106 Name: "nvidia/gpu", 2107 Count: 4, 2108 Constraints: []*structs.Constraint{ 2109 { 2110 LTarget: "x", 2111 RTarget: "y", 2112 Operand: "z", 2113 }, 2114 }, 2115 Affinities: []*structs.Affinity{ 2116 { 2117 LTarget: "a", 2118 RTarget: "b", 2119 Operand: "c", 2120 Weight: 50, 2121 }, 2122 }, 2123 }, 2124 { 2125 Name: "gpu", 2126 Count: 1, 2127 }, 2128 }, 2129 }, 2130 Meta: map[string]string{ 2131 "lol": "code", 2132 }, 2133 KillTimeout: 10 * time.Second, 2134 KillSignal: "SIGQUIT", 2135 LogConfig: &structs.LogConfig{ 2136 MaxFiles: 10, 2137 MaxFileSizeMB: 100, 2138 }, 2139 Artifacts: []*structs.TaskArtifact{ 2140 { 2141 GetterSource: "source", 2142 GetterOptions: map[string]string{ 2143 "a": "b", 2144 }, 2145 GetterMode: "dir", 2146 RelativeDest: "dest", 2147 }, 2148 }, 2149 Vault: &structs.Vault{ 2150 Policies: []string{"a", "b", "c"}, 2151 Env: true, 2152 ChangeMode: "c", 2153 ChangeSignal: "sighup", 2154 }, 2155 Templates: []*structs.Template{ 2156 { 2157 SourcePath: "source", 2158 DestPath: "dest", 2159 EmbeddedTmpl: "embedded", 2160 ChangeMode: "change", 2161 ChangeSignal: "SIGNAL", 2162 Splay: 1 * time.Minute, 2163 Perms: "666", 2164 LeftDelim: "abc", 2165 RightDelim: "def", 2166 Envvars: true, 2167 }, 2168 }, 2169 DispatchPayload: &structs.DispatchPayloadConfig{ 2170 File: "fileA", 2171 }, 2172 }, 2173 }, 2174 }, 2175 }, 2176 2177 ConsulToken: "abc123", 2178 VaultToken: "def456", 2179 } 2180 2181 structsJob := ApiJobToStructJob(apiJob) 2182 2183 if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { 2184 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 2185 } 2186 2187 systemAPIJob := &api.Job{ 2188 Stop: helper.BoolToPtr(true), 2189 Region: helper.StringToPtr("global"), 2190 Namespace: helper.StringToPtr("foo"), 2191 ID: helper.StringToPtr("foo"), 2192 ParentID: helper.StringToPtr("lol"), 2193 Name: helper.StringToPtr("name"), 2194 Type: helper.StringToPtr("system"), 2195 Priority: helper.IntToPtr(50), 2196 AllAtOnce: helper.BoolToPtr(true), 2197 Datacenters: []string{"dc1", "dc2"}, 2198 Constraints: []*api.Constraint{ 2199 { 2200 LTarget: "a", 2201 RTarget: "b", 2202 Operand: "c", 2203 }, 2204 }, 2205 TaskGroups: []*api.TaskGroup{ 2206 { 2207 Name: helper.StringToPtr("group1"), 2208 Count: helper.IntToPtr(5), 2209 Constraints: []*api.Constraint{ 2210 { 2211 LTarget: "x", 2212 RTarget: "y", 2213 Operand: "z", 2214 }, 2215 }, 2216 RestartPolicy: &api.RestartPolicy{ 2217 Interval: helper.TimeToPtr(1 * time.Second), 2218 Attempts: helper.IntToPtr(5), 2219 Delay: helper.TimeToPtr(10 * time.Second), 2220 Mode: helper.StringToPtr("delay"), 2221 }, 2222 EphemeralDisk: &api.EphemeralDisk{ 2223 SizeMB: helper.IntToPtr(100), 2224 Sticky: helper.BoolToPtr(true), 2225 Migrate: helper.BoolToPtr(true), 2226 }, 2227 Meta: map[string]string{ 2228 "key": "value", 2229 }, 2230 Tasks: []*api.Task{ 2231 { 2232 Name: "task1", 2233 Leader: true, 2234 Driver: "docker", 2235 User: "mary", 2236 Config: map[string]interface{}{ 2237 "lol": "code", 2238 }, 2239 Env: map[string]string{ 2240 "hello": "world", 2241 }, 2242 Constraints: []*api.Constraint{ 2243 { 2244 LTarget: "x", 2245 RTarget: "y", 2246 Operand: "z", 2247 }, 2248 }, 2249 Resources: &api.Resources{ 2250 CPU: helper.IntToPtr(100), 2251 MemoryMB: helper.IntToPtr(10), 2252 Networks: []*api.NetworkResource{ 2253 { 2254 IP: "10.10.11.1", 2255 MBits: helper.IntToPtr(10), 2256 ReservedPorts: []api.Port{ 2257 { 2258 Label: "http", 2259 Value: 80, 2260 }, 2261 }, 2262 DynamicPorts: []api.Port{ 2263 { 2264 Label: "ssh", 2265 Value: 2000, 2266 }, 2267 }, 2268 }, 2269 }, 2270 }, 2271 Meta: map[string]string{ 2272 "lol": "code", 2273 }, 2274 KillTimeout: helper.TimeToPtr(10 * time.Second), 2275 KillSignal: "SIGQUIT", 2276 LogConfig: &api.LogConfig{ 2277 MaxFiles: helper.IntToPtr(10), 2278 MaxFileSizeMB: helper.IntToPtr(100), 2279 }, 2280 Artifacts: []*api.TaskArtifact{ 2281 { 2282 GetterSource: helper.StringToPtr("source"), 2283 GetterOptions: map[string]string{ 2284 "a": "b", 2285 }, 2286 GetterMode: helper.StringToPtr("dir"), 2287 RelativeDest: helper.StringToPtr("dest"), 2288 }, 2289 }, 2290 DispatchPayload: &api.DispatchPayloadConfig{ 2291 File: "fileA", 2292 }, 2293 }, 2294 }, 2295 }, 2296 }, 2297 Status: helper.StringToPtr("status"), 2298 StatusDescription: helper.StringToPtr("status_desc"), 2299 Version: helper.Uint64ToPtr(10), 2300 CreateIndex: helper.Uint64ToPtr(1), 2301 ModifyIndex: helper.Uint64ToPtr(3), 2302 JobModifyIndex: helper.Uint64ToPtr(5), 2303 } 2304 2305 expectedSystemJob := &structs.Job{ 2306 Stop: true, 2307 Region: "global", 2308 Namespace: "foo", 2309 ID: "foo", 2310 ParentID: "lol", 2311 Name: "name", 2312 Type: "system", 2313 Priority: 50, 2314 AllAtOnce: true, 2315 Datacenters: []string{"dc1", "dc2"}, 2316 Constraints: []*structs.Constraint{ 2317 { 2318 LTarget: "a", 2319 RTarget: "b", 2320 Operand: "c", 2321 }, 2322 }, 2323 TaskGroups: []*structs.TaskGroup{ 2324 { 2325 Name: "group1", 2326 Count: 5, 2327 Constraints: []*structs.Constraint{ 2328 { 2329 LTarget: "x", 2330 RTarget: "y", 2331 Operand: "z", 2332 }, 2333 }, 2334 RestartPolicy: &structs.RestartPolicy{ 2335 Interval: 1 * time.Second, 2336 Attempts: 5, 2337 Delay: 10 * time.Second, 2338 Mode: "delay", 2339 }, 2340 EphemeralDisk: &structs.EphemeralDisk{ 2341 SizeMB: 100, 2342 Sticky: true, 2343 Migrate: true, 2344 }, 2345 Meta: map[string]string{ 2346 "key": "value", 2347 }, 2348 Tasks: []*structs.Task{ 2349 { 2350 Name: "task1", 2351 Driver: "docker", 2352 Leader: true, 2353 User: "mary", 2354 Config: map[string]interface{}{ 2355 "lol": "code", 2356 }, 2357 Constraints: []*structs.Constraint{ 2358 { 2359 LTarget: "x", 2360 RTarget: "y", 2361 Operand: "z", 2362 }, 2363 }, 2364 Env: map[string]string{ 2365 "hello": "world", 2366 }, 2367 Resources: &structs.Resources{ 2368 CPU: 100, 2369 MemoryMB: 10, 2370 Networks: []*structs.NetworkResource{ 2371 { 2372 IP: "10.10.11.1", 2373 MBits: 10, 2374 ReservedPorts: []structs.Port{ 2375 { 2376 Label: "http", 2377 Value: 80, 2378 }, 2379 }, 2380 DynamicPorts: []structs.Port{ 2381 { 2382 Label: "ssh", 2383 Value: 2000, 2384 }, 2385 }, 2386 }, 2387 }, 2388 }, 2389 RestartPolicy: &structs.RestartPolicy{ 2390 Interval: 1 * time.Second, 2391 Attempts: 5, 2392 Delay: 10 * time.Second, 2393 Mode: "delay", 2394 }, 2395 Meta: map[string]string{ 2396 "lol": "code", 2397 }, 2398 KillTimeout: 10 * time.Second, 2399 KillSignal: "SIGQUIT", 2400 LogConfig: &structs.LogConfig{ 2401 MaxFiles: 10, 2402 MaxFileSizeMB: 100, 2403 }, 2404 Artifacts: []*structs.TaskArtifact{ 2405 { 2406 GetterSource: "source", 2407 GetterOptions: map[string]string{ 2408 "a": "b", 2409 }, 2410 GetterMode: "dir", 2411 RelativeDest: "dest", 2412 }, 2413 }, 2414 DispatchPayload: &structs.DispatchPayloadConfig{ 2415 File: "fileA", 2416 }, 2417 }, 2418 }, 2419 }, 2420 }, 2421 } 2422 2423 systemStructsJob := ApiJobToStructJob(systemAPIJob) 2424 2425 if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 { 2426 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 2427 } 2428 } 2429 2430 func TestJobs_ApiJobToStructsJobUpdate(t *testing.T) { 2431 apiJob := &api.Job{ 2432 Update: &api.UpdateStrategy{ 2433 Stagger: helper.TimeToPtr(1 * time.Second), 2434 MaxParallel: helper.IntToPtr(5), 2435 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 2436 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 2437 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 2438 ProgressDeadline: helper.TimeToPtr(3 * time.Minute), 2439 AutoRevert: helper.BoolToPtr(false), 2440 AutoPromote: nil, 2441 Canary: helper.IntToPtr(1), 2442 }, 2443 TaskGroups: []*api.TaskGroup{ 2444 { 2445 Update: &api.UpdateStrategy{ 2446 Canary: helper.IntToPtr(2), 2447 AutoRevert: helper.BoolToPtr(true), 2448 }, 2449 }, { 2450 Update: &api.UpdateStrategy{ 2451 Canary: helper.IntToPtr(3), 2452 AutoPromote: helper.BoolToPtr(true), 2453 }, 2454 }, 2455 }, 2456 } 2457 2458 structsJob := ApiJobToStructJob(apiJob) 2459 2460 // Update has been moved from job down to the groups 2461 jobUpdate := structs.UpdateStrategy{ 2462 Stagger: 1000000000, 2463 MaxParallel: 5, 2464 HealthCheck: "", 2465 MinHealthyTime: 0, 2466 HealthyDeadline: 0, 2467 ProgressDeadline: 0, 2468 AutoRevert: false, 2469 AutoPromote: false, 2470 Canary: 0, 2471 } 2472 2473 // But the groups inherit settings from the job update 2474 group1 := structs.UpdateStrategy{ 2475 Stagger: 1000000000, 2476 MaxParallel: 5, 2477 HealthCheck: "manual", 2478 MinHealthyTime: 60000000000, 2479 HealthyDeadline: 180000000000, 2480 ProgressDeadline: 180000000000, 2481 AutoRevert: true, 2482 AutoPromote: false, 2483 Canary: 2, 2484 } 2485 2486 group2 := structs.UpdateStrategy{ 2487 Stagger: 1000000000, 2488 MaxParallel: 5, 2489 HealthCheck: "manual", 2490 MinHealthyTime: 60000000000, 2491 HealthyDeadline: 180000000000, 2492 ProgressDeadline: 180000000000, 2493 AutoRevert: false, 2494 AutoPromote: true, 2495 Canary: 3, 2496 } 2497 2498 require.Equal(t, jobUpdate, structsJob.Update) 2499 require.Equal(t, group1, *structsJob.TaskGroups[0].Update) 2500 require.Equal(t, group2, *structsJob.TaskGroups[1].Update) 2501 } 2502 2503 // TestHTTP_JobValidate_SystemMigrate asserts that a system job with a migrate 2504 // stanza fails to validate but does not panic (see #5477). 2505 func TestHTTP_JobValidate_SystemMigrate(t *testing.T) { 2506 t.Parallel() 2507 httpTest(t, nil, func(s *TestAgent) { 2508 // Create the job 2509 job := &api.Job{ 2510 Region: helper.StringToPtr("global"), 2511 Datacenters: []string{"dc1"}, 2512 ID: helper.StringToPtr("systemmigrate"), 2513 Name: helper.StringToPtr("systemmigrate"), 2514 TaskGroups: []*api.TaskGroup{ 2515 {Name: helper.StringToPtr("web")}, 2516 }, 2517 2518 // System job... 2519 Type: helper.StringToPtr("system"), 2520 2521 // ...with an empty migrate stanza 2522 Migrate: &api.MigrateStrategy{}, 2523 } 2524 2525 args := api.JobValidateRequest{ 2526 Job: job, 2527 WriteRequest: api.WriteRequest{Region: "global"}, 2528 } 2529 buf := encodeReq(args) 2530 2531 // Make the HTTP request 2532 req, err := http.NewRequest("PUT", "/v1/validate/job", buf) 2533 require.NoError(t, err) 2534 respW := httptest.NewRecorder() 2535 2536 // Make the request 2537 obj, err := s.Server.ValidateJobRequest(respW, req) 2538 require.NoError(t, err) 2539 2540 // Check the response 2541 resp := obj.(structs.JobValidateResponse) 2542 require.Contains(t, resp.Error, `Job type "system" does not allow migrate block`) 2543 }) 2544 } 2545 2546 func TestConversion_dereferenceInt(t *testing.T) { 2547 t.Parallel() 2548 require.Equal(t, 0, dereferenceInt(nil)) 2549 require.Equal(t, 42, dereferenceInt(helper.IntToPtr(42))) 2550 } 2551 2552 func TestConversion_apiLogConfigToStructs(t *testing.T) { 2553 t.Parallel() 2554 require.Nil(t, apiLogConfigToStructs(nil)) 2555 require.Equal(t, &structs.LogConfig{ 2556 MaxFiles: 2, 2557 MaxFileSizeMB: 8, 2558 }, apiLogConfigToStructs(&api.LogConfig{ 2559 MaxFiles: helper.IntToPtr(2), 2560 MaxFileSizeMB: helper.IntToPtr(8), 2561 })) 2562 } 2563 2564 func TestConversion_apiConnectSidecarTaskToStructs(t *testing.T) { 2565 t.Parallel() 2566 require.Nil(t, apiConnectSidecarTaskToStructs(nil)) 2567 delay := time.Duration(200) 2568 timeout := time.Duration(1000) 2569 config := make(map[string]interface{}) 2570 env := make(map[string]string) 2571 meta := make(map[string]string) 2572 require.Equal(t, &structs.SidecarTask{ 2573 Name: "name", 2574 Driver: "driver", 2575 User: "user", 2576 Config: config, 2577 Env: env, 2578 Resources: &structs.Resources{ 2579 CPU: 1, 2580 MemoryMB: 128, 2581 }, 2582 Meta: meta, 2583 KillTimeout: &timeout, 2584 LogConfig: &structs.LogConfig{ 2585 MaxFiles: 2, 2586 MaxFileSizeMB: 8, 2587 }, 2588 ShutdownDelay: &delay, 2589 KillSignal: "SIGTERM", 2590 }, apiConnectSidecarTaskToStructs(&api.SidecarTask{ 2591 Name: "name", 2592 Driver: "driver", 2593 User: "user", 2594 Config: config, 2595 Env: env, 2596 Resources: &api.Resources{ 2597 CPU: helper.IntToPtr(1), 2598 MemoryMB: helper.IntToPtr(128), 2599 }, 2600 Meta: meta, 2601 KillTimeout: &timeout, 2602 LogConfig: &api.LogConfig{ 2603 MaxFiles: helper.IntToPtr(2), 2604 MaxFileSizeMB: helper.IntToPtr(8), 2605 }, 2606 ShutdownDelay: &delay, 2607 KillSignal: "SIGTERM", 2608 })) 2609 } 2610 2611 func TestConversion_apiConsulExposePathsToStructs(t *testing.T) { 2612 t.Parallel() 2613 require.Nil(t, apiConsulExposePathsToStructs(nil)) 2614 require.Nil(t, apiConsulExposePathsToStructs(make([]*api.ConsulExposePath, 0))) 2615 require.Equal(t, []structs.ConsulExposePath{{ 2616 Path: "/health", 2617 Protocol: "http", 2618 LocalPathPort: 8080, 2619 ListenerPort: "hcPort", 2620 }}, apiConsulExposePathsToStructs([]*api.ConsulExposePath{{ 2621 Path: "/health", 2622 Protocol: "http", 2623 LocalPathPort: 8080, 2624 ListenerPort: "hcPort", 2625 }})) 2626 } 2627 2628 func TestConversion_apiConsulExposeConfigToStructs(t *testing.T) { 2629 t.Parallel() 2630 require.Nil(t, apiConsulExposeConfigToStructs(nil)) 2631 require.Equal(t, &structs.ConsulExposeConfig{ 2632 Paths: []structs.ConsulExposePath{{Path: "/health"}}, 2633 }, apiConsulExposeConfigToStructs(&api.ConsulExposeConfig{ 2634 Path: []*api.ConsulExposePath{{Path: "/health"}}, 2635 })) 2636 } 2637 2638 func TestConversion_apiUpstreamsToStructs(t *testing.T) { 2639 t.Parallel() 2640 require.Nil(t, apiUpstreamsToStructs(nil)) 2641 require.Nil(t, apiUpstreamsToStructs(make([]*api.ConsulUpstream, 0))) 2642 require.Equal(t, []structs.ConsulUpstream{{ 2643 DestinationName: "upstream", 2644 LocalBindPort: 8000, 2645 }}, apiUpstreamsToStructs([]*api.ConsulUpstream{{ 2646 DestinationName: "upstream", 2647 LocalBindPort: 8000, 2648 }})) 2649 } 2650 2651 func TestConversion_apiConnectSidecarServiceProxyToStructs(t *testing.T) { 2652 t.Parallel() 2653 require.Nil(t, apiConnectSidecarServiceProxyToStructs(nil)) 2654 config := make(map[string]interface{}) 2655 require.Equal(t, &structs.ConsulProxy{ 2656 LocalServiceAddress: "192.168.30.1", 2657 LocalServicePort: 9000, 2658 Config: config, 2659 Upstreams: []structs.ConsulUpstream{{ 2660 DestinationName: "upstream", 2661 }}, 2662 Expose: &structs.ConsulExposeConfig{ 2663 Paths: []structs.ConsulExposePath{{Path: "/health"}}, 2664 }, 2665 }, apiConnectSidecarServiceProxyToStructs(&api.ConsulProxy{ 2666 LocalServiceAddress: "192.168.30.1", 2667 LocalServicePort: 9000, 2668 Config: config, 2669 Upstreams: []*api.ConsulUpstream{{ 2670 DestinationName: "upstream", 2671 }}, 2672 ExposeConfig: &api.ConsulExposeConfig{ 2673 Path: []*api.ConsulExposePath{{ 2674 Path: "/health", 2675 }}, 2676 }, 2677 })) 2678 } 2679 2680 func TestConversion_apiConnectSidecarServiceToStructs(t *testing.T) { 2681 t.Parallel() 2682 require.Nil(t, apiConnectSidecarTaskToStructs(nil)) 2683 require.Equal(t, &structs.ConsulSidecarService{ 2684 Tags: []string{"foo"}, 2685 Port: "myPort", 2686 Proxy: &structs.ConsulProxy{ 2687 LocalServiceAddress: "192.168.30.1", 2688 }, 2689 }, apiConnectSidecarServiceToStructs(&api.ConsulSidecarService{ 2690 Tags: []string{"foo"}, 2691 Port: "myPort", 2692 Proxy: &api.ConsulProxy{ 2693 LocalServiceAddress: "192.168.30.1", 2694 }, 2695 })) 2696 } 2697 2698 func TestConversion_ApiConsulConnectToStructs(t *testing.T) { 2699 t.Parallel() 2700 require.Nil(t, ApiConsulConnectToStructs(nil)) 2701 require.Equal(t, &structs.ConsulConnect{ 2702 Native: false, 2703 SidecarService: &structs.ConsulSidecarService{Port: "myPort"}, 2704 SidecarTask: &structs.SidecarTask{Name: "task"}, 2705 }, ApiConsulConnectToStructs(&api.ConsulConnect{ 2706 Native: false, 2707 SidecarService: &api.ConsulSidecarService{Port: "myPort"}, 2708 SidecarTask: &api.SidecarTask{Name: "task"}, 2709 })) 2710 }