github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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 ) 19 20 func TestHTTP_JobsList(t *testing.T) { 21 t.Parallel() 22 httpTest(t, nil, func(s *TestAgent) { 23 for i := 0; i < 3; i++ { 24 // Create the job 25 job := mock.Job() 26 args := structs.JobRegisterRequest{ 27 Job: job, 28 WriteRequest: structs.WriteRequest{ 29 Region: "global", 30 Namespace: structs.DefaultNamespace, 31 }, 32 } 33 var resp structs.JobRegisterResponse 34 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 } 38 39 // Make the HTTP request 40 req, err := http.NewRequest("GET", "/v1/jobs", nil) 41 if err != nil { 42 t.Fatalf("err: %v", err) 43 } 44 respW := httptest.NewRecorder() 45 46 // Make the request 47 obj, err := s.Server.JobsRequest(respW, req) 48 if err != nil { 49 t.Fatalf("err: %v", err) 50 } 51 52 // Check for the index 53 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 54 t.Fatalf("missing index") 55 } 56 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 57 t.Fatalf("missing known leader") 58 } 59 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 60 t.Fatalf("missing last contact") 61 } 62 63 // Check the job 64 j := obj.([]*structs.JobListStub) 65 if len(j) != 3 { 66 t.Fatalf("bad: %#v", j) 67 } 68 }) 69 } 70 71 func TestHTTP_PrefixJobsList(t *testing.T) { 72 ids := []string{ 73 "aaaaaaaa-e8f7-fd38-c855-ab94ceb89706", 74 "aabbbbbb-e8f7-fd38-c855-ab94ceb89706", 75 "aabbcccc-e8f7-fd38-c855-ab94ceb89706", 76 } 77 t.Parallel() 78 httpTest(t, nil, func(s *TestAgent) { 79 for i := 0; i < 3; i++ { 80 // Create the job 81 job := mock.Job() 82 job.ID = ids[i] 83 job.TaskGroups[0].Count = 1 84 args := structs.JobRegisterRequest{ 85 Job: job, 86 WriteRequest: structs.WriteRequest{ 87 Region: "global", 88 Namespace: structs.DefaultNamespace, 89 }, 90 } 91 var resp structs.JobRegisterResponse 92 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 93 t.Fatalf("err: %v", err) 94 } 95 } 96 97 // Make the HTTP request 98 req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil) 99 if err != nil { 100 t.Fatalf("err: %v", err) 101 } 102 respW := httptest.NewRecorder() 103 104 // Make the request 105 obj, err := s.Server.JobsRequest(respW, req) 106 if err != nil { 107 t.Fatalf("err: %v", err) 108 } 109 110 // Check for the index 111 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 112 t.Fatalf("missing index") 113 } 114 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 115 t.Fatalf("missing known leader") 116 } 117 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 118 t.Fatalf("missing last contact") 119 } 120 121 // Check the job 122 j := obj.([]*structs.JobListStub) 123 if len(j) != 2 { 124 t.Fatalf("bad: %#v", j) 125 } 126 }) 127 } 128 129 func TestHTTP_JobsRegister(t *testing.T) { 130 t.Parallel() 131 httpTest(t, nil, func(s *TestAgent) { 132 // Create the job 133 job := api.MockJob() 134 args := api.JobRegisterRequest{ 135 Job: job, 136 WriteRequest: api.WriteRequest{Region: "global"}, 137 } 138 buf := encodeReq(args) 139 140 // Make the HTTP request 141 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 142 if err != nil { 143 t.Fatalf("err: %v", err) 144 } 145 respW := httptest.NewRecorder() 146 147 // Make the request 148 obj, err := s.Server.JobsRequest(respW, req) 149 if err != nil { 150 t.Fatalf("err: %v", err) 151 } 152 153 // Check the response 154 dereg := obj.(structs.JobRegisterResponse) 155 if dereg.EvalID == "" { 156 t.Fatalf("bad: %v", dereg) 157 } 158 159 // Check for the index 160 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 161 t.Fatalf("missing index") 162 } 163 164 // Check the job is registered 165 getReq := structs.JobSpecificRequest{ 166 JobID: *job.ID, 167 QueryOptions: structs.QueryOptions{ 168 Region: "global", 169 Namespace: structs.DefaultNamespace, 170 }, 171 } 172 var getResp structs.SingleJobResponse 173 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 174 t.Fatalf("err: %v", err) 175 } 176 177 if getResp.Job == nil { 178 t.Fatalf("job does not exist") 179 } 180 }) 181 } 182 183 // Test that ACL token is properly threaded through to the RPC endpoint 184 func TestHTTP_JobsRegister_ACL(t *testing.T) { 185 t.Parallel() 186 httpACLTest(t, nil, func(s *TestAgent) { 187 // Create the job 188 job := api.MockJob() 189 args := api.JobRegisterRequest{ 190 Job: job, 191 WriteRequest: api.WriteRequest{ 192 Region: "global", 193 }, 194 } 195 buf := encodeReq(args) 196 197 // Make the HTTP request 198 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 199 if err != nil { 200 t.Fatalf("err: %v", err) 201 } 202 respW := httptest.NewRecorder() 203 setToken(req, s.RootToken) 204 205 // Make the request 206 obj, err := s.Server.JobsRequest(respW, req) 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 assert.NotNil(t, obj) 211 }) 212 } 213 214 func TestHTTP_JobsRegister_Defaulting(t *testing.T) { 215 t.Parallel() 216 httpTest(t, nil, func(s *TestAgent) { 217 // Create the job 218 job := api.MockJob() 219 220 // Do not set its priority 221 job.Priority = nil 222 223 args := api.JobRegisterRequest{ 224 Job: job, 225 WriteRequest: api.WriteRequest{Region: "global"}, 226 } 227 buf := encodeReq(args) 228 229 // Make the HTTP request 230 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 231 if err != nil { 232 t.Fatalf("err: %v", err) 233 } 234 respW := httptest.NewRecorder() 235 236 // Make the request 237 obj, err := s.Server.JobsRequest(respW, req) 238 if err != nil { 239 t.Fatalf("err: %v", err) 240 } 241 242 // Check the response 243 dereg := obj.(structs.JobRegisterResponse) 244 if dereg.EvalID == "" { 245 t.Fatalf("bad: %v", dereg) 246 } 247 248 // Check for the index 249 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 250 t.Fatalf("missing index") 251 } 252 253 // Check the job is registered 254 getReq := structs.JobSpecificRequest{ 255 JobID: *job.ID, 256 QueryOptions: structs.QueryOptions{ 257 Region: "global", 258 Namespace: structs.DefaultNamespace, 259 }, 260 } 261 var getResp structs.SingleJobResponse 262 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 263 t.Fatalf("err: %v", err) 264 } 265 266 if getResp.Job == nil { 267 t.Fatalf("job does not exist") 268 } 269 if getResp.Job.Priority != 50 { 270 t.Fatalf("job didn't get defaulted") 271 } 272 }) 273 } 274 275 func TestHTTP_JobsParse(t *testing.T) { 276 t.Parallel() 277 httpTest(t, nil, func(s *TestAgent) { 278 buf := encodeReq(api.JobsParseRequest{JobHCL: mock.HCL()}) 279 req, err := http.NewRequest("POST", "/v1/jobs/parse", buf) 280 if err != nil { 281 t.Fatalf("err: %v", err) 282 } 283 284 respW := httptest.NewRecorder() 285 286 obj, err := s.Server.JobsParseRequest(respW, req) 287 if err != nil { 288 t.Fatalf("err: %v", err) 289 } 290 if obj == nil { 291 t.Fatal("response should not be nil") 292 } 293 294 job := obj.(*api.Job) 295 expected := mock.Job() 296 if job.Name == nil || *job.Name != expected.Name { 297 t.Fatalf("job name is '%s', expected '%s'", *job.Name, expected.Name) 298 } 299 300 if job.Datacenters == nil || 301 job.Datacenters[0] != expected.Datacenters[0] { 302 t.Fatalf("job datacenters is '%s', expected '%s'", 303 job.Datacenters[0], expected.Datacenters[0]) 304 } 305 }) 306 } 307 func TestHTTP_JobQuery(t *testing.T) { 308 t.Parallel() 309 httpTest(t, nil, func(s *TestAgent) { 310 // Create the job 311 job := mock.Job() 312 args := structs.JobRegisterRequest{ 313 Job: job, 314 WriteRequest: structs.WriteRequest{ 315 Region: "global", 316 Namespace: structs.DefaultNamespace, 317 }, 318 } 319 var resp structs.JobRegisterResponse 320 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 321 t.Fatalf("err: %v", err) 322 } 323 324 // Make the HTTP request 325 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 326 if err != nil { 327 t.Fatalf("err: %v", err) 328 } 329 respW := httptest.NewRecorder() 330 331 // Make the request 332 obj, err := s.Server.JobSpecificRequest(respW, req) 333 if err != nil { 334 t.Fatalf("err: %v", err) 335 } 336 337 // Check for the index 338 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 339 t.Fatalf("missing index") 340 } 341 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 342 t.Fatalf("missing known leader") 343 } 344 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 345 t.Fatalf("missing last contact") 346 } 347 348 // Check the job 349 j := obj.(*structs.Job) 350 if j.ID != job.ID { 351 t.Fatalf("bad: %#v", j) 352 } 353 }) 354 } 355 356 func TestHTTP_JobQuery_Payload(t *testing.T) { 357 t.Parallel() 358 httpTest(t, nil, func(s *TestAgent) { 359 // Create the job 360 job := mock.Job() 361 362 // Insert Payload compressed 363 expected := []byte("hello world") 364 compressed := snappy.Encode(nil, expected) 365 job.Payload = compressed 366 367 // Directly manipulate the state 368 state := s.Agent.server.State() 369 if err := state.UpsertJob(1000, job); err != nil { 370 t.Fatalf("Failed to upsert job: %v", err) 371 } 372 373 // Make the HTTP request 374 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 375 if err != nil { 376 t.Fatalf("err: %v", err) 377 } 378 respW := httptest.NewRecorder() 379 380 // Make the request 381 obj, err := s.Server.JobSpecificRequest(respW, req) 382 if err != nil { 383 t.Fatalf("err: %v", err) 384 } 385 386 // Check for the index 387 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 388 t.Fatalf("missing index") 389 } 390 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 391 t.Fatalf("missing known leader") 392 } 393 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 394 t.Fatalf("missing last contact") 395 } 396 397 // Check the job 398 j := obj.(*structs.Job) 399 if j.ID != job.ID { 400 t.Fatalf("bad: %#v", j) 401 } 402 403 // Check the payload is decompressed 404 if !reflect.DeepEqual(j.Payload, expected) { 405 t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected) 406 } 407 }) 408 } 409 410 func TestHTTP_JobUpdate(t *testing.T) { 411 t.Parallel() 412 httpTest(t, nil, func(s *TestAgent) { 413 // Create the job 414 job := api.MockJob() 415 args := api.JobRegisterRequest{ 416 Job: job, 417 WriteRequest: api.WriteRequest{ 418 Region: "global", 419 Namespace: api.DefaultNamespace, 420 }, 421 } 422 buf := encodeReq(args) 423 424 // Make the HTTP request 425 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf) 426 if err != nil { 427 t.Fatalf("err: %v", err) 428 } 429 respW := httptest.NewRecorder() 430 431 // Make the request 432 obj, err := s.Server.JobSpecificRequest(respW, req) 433 if err != nil { 434 t.Fatalf("err: %v", err) 435 } 436 437 // Check the response 438 dereg := obj.(structs.JobRegisterResponse) 439 if dereg.EvalID == "" { 440 t.Fatalf("bad: %v", dereg) 441 } 442 443 // Check for the index 444 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 445 t.Fatalf("missing index") 446 } 447 448 // Check the job is registered 449 getReq := structs.JobSpecificRequest{ 450 JobID: *job.ID, 451 QueryOptions: structs.QueryOptions{ 452 Region: "global", 453 Namespace: structs.DefaultNamespace, 454 }, 455 } 456 var getResp structs.SingleJobResponse 457 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 458 t.Fatalf("err: %v", err) 459 } 460 461 if getResp.Job == nil { 462 t.Fatalf("job does not exist") 463 } 464 }) 465 } 466 467 func TestHTTP_JobDelete(t *testing.T) { 468 t.Parallel() 469 httpTest(t, nil, func(s *TestAgent) { 470 // Create the job 471 job := mock.Job() 472 args := structs.JobRegisterRequest{ 473 Job: job, 474 WriteRequest: structs.WriteRequest{ 475 Region: "global", 476 Namespace: structs.DefaultNamespace, 477 }, 478 } 479 var resp structs.JobRegisterResponse 480 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 481 t.Fatalf("err: %v", err) 482 } 483 484 // Make the HTTP request to do a soft delete 485 req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil) 486 if err != nil { 487 t.Fatalf("err: %v", err) 488 } 489 respW := httptest.NewRecorder() 490 491 // Make the request 492 obj, err := s.Server.JobSpecificRequest(respW, req) 493 if err != nil { 494 t.Fatalf("err: %v", err) 495 } 496 497 // Check the response 498 dereg := obj.(structs.JobDeregisterResponse) 499 if dereg.EvalID == "" { 500 t.Fatalf("bad: %v", dereg) 501 } 502 503 // Check for the index 504 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 505 t.Fatalf("missing index") 506 } 507 508 // Check the job is still queryable 509 getReq1 := structs.JobSpecificRequest{ 510 JobID: job.ID, 511 QueryOptions: structs.QueryOptions{ 512 Region: "global", 513 Namespace: structs.DefaultNamespace, 514 }, 515 } 516 var getResp1 structs.SingleJobResponse 517 if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil { 518 t.Fatalf("err: %v", err) 519 } 520 if getResp1.Job == nil { 521 t.Fatalf("job doesn't exists") 522 } 523 if !getResp1.Job.Stop { 524 t.Fatalf("job should be marked as stop") 525 } 526 527 // Make the HTTP request to do a purge delete 528 req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil) 529 if err != nil { 530 t.Fatalf("err: %v", err) 531 } 532 respW.Flush() 533 534 // Make the request 535 obj, err = s.Server.JobSpecificRequest(respW, req2) 536 if err != nil { 537 t.Fatalf("err: %v", err) 538 } 539 540 // Check the response 541 dereg = obj.(structs.JobDeregisterResponse) 542 if dereg.EvalID == "" { 543 t.Fatalf("bad: %v", dereg) 544 } 545 546 // Check for the index 547 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 548 t.Fatalf("missing index") 549 } 550 551 // Check the job is gone 552 getReq2 := structs.JobSpecificRequest{ 553 JobID: job.ID, 554 QueryOptions: structs.QueryOptions{ 555 Region: "global", 556 Namespace: structs.DefaultNamespace, 557 }, 558 } 559 var getResp2 structs.SingleJobResponse 560 if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil { 561 t.Fatalf("err: %v", err) 562 } 563 if getResp2.Job != nil { 564 t.Fatalf("job still exists") 565 } 566 }) 567 } 568 569 func TestHTTP_JobForceEvaluate(t *testing.T) { 570 t.Parallel() 571 httpTest(t, nil, func(s *TestAgent) { 572 // Create the job 573 job := mock.Job() 574 args := structs.JobRegisterRequest{ 575 Job: job, 576 WriteRequest: structs.WriteRequest{ 577 Region: "global", 578 Namespace: structs.DefaultNamespace, 579 }, 580 } 581 var resp structs.JobRegisterResponse 582 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 583 t.Fatalf("err: %v", err) 584 } 585 586 // Make the HTTP request 587 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil) 588 if err != nil { 589 t.Fatalf("err: %v", err) 590 } 591 respW := httptest.NewRecorder() 592 593 // Make the request 594 obj, err := s.Server.JobSpecificRequest(respW, req) 595 if err != nil { 596 t.Fatalf("err: %v", err) 597 } 598 599 // Check the response 600 reg := obj.(structs.JobRegisterResponse) 601 if reg.EvalID == "" { 602 t.Fatalf("bad: %v", reg) 603 } 604 605 // Check for the index 606 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 607 t.Fatalf("missing index") 608 } 609 }) 610 } 611 612 func TestHTTP_JobEvaluate_ForceReschedule(t *testing.T) { 613 t.Parallel() 614 httpTest(t, nil, func(s *TestAgent) { 615 // Create the job 616 job := mock.Job() 617 args := structs.JobRegisterRequest{ 618 Job: job, 619 WriteRequest: structs.WriteRequest{ 620 Region: "global", 621 Namespace: structs.DefaultNamespace, 622 }, 623 } 624 var resp structs.JobRegisterResponse 625 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 626 t.Fatalf("err: %v", err) 627 } 628 jobEvalReq := api.JobEvaluateRequest{ 629 JobID: job.ID, 630 EvalOptions: api.EvalOptions{ 631 ForceReschedule: true, 632 }, 633 } 634 635 buf := encodeReq(jobEvalReq) 636 637 // Make the HTTP request 638 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", buf) 639 if err != nil { 640 t.Fatalf("err: %v", err) 641 } 642 respW := httptest.NewRecorder() 643 644 // Make the request 645 obj, err := s.Server.JobSpecificRequest(respW, req) 646 if err != nil { 647 t.Fatalf("err: %v", err) 648 } 649 650 // Check the response 651 reg := obj.(structs.JobRegisterResponse) 652 if reg.EvalID == "" { 653 t.Fatalf("bad: %v", reg) 654 } 655 656 // Check for the index 657 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 658 t.Fatalf("missing index") 659 } 660 }) 661 } 662 663 func TestHTTP_JobEvaluations(t *testing.T) { 664 t.Parallel() 665 httpTest(t, nil, func(s *TestAgent) { 666 // Create the job 667 job := mock.Job() 668 args := structs.JobRegisterRequest{ 669 Job: job, 670 WriteRequest: structs.WriteRequest{ 671 Region: "global", 672 Namespace: structs.DefaultNamespace, 673 }, 674 } 675 var resp structs.JobRegisterResponse 676 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 677 t.Fatalf("err: %v", err) 678 } 679 680 // Make the HTTP request 681 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil) 682 if err != nil { 683 t.Fatalf("err: %v", err) 684 } 685 respW := httptest.NewRecorder() 686 687 // Make the request 688 obj, err := s.Server.JobSpecificRequest(respW, req) 689 if err != nil { 690 t.Fatalf("err: %v", err) 691 } 692 693 // Check the response 694 evals := obj.([]*structs.Evaluation) 695 // Can be multiple evals, use the last one, since they are in order 696 idx := len(evals) - 1 697 if len(evals) < 0 || evals[idx].ID != resp.EvalID { 698 t.Fatalf("bad: %v", evals) 699 } 700 701 // Check for the index 702 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 703 t.Fatalf("missing index") 704 } 705 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 706 t.Fatalf("missing known leader") 707 } 708 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 709 t.Fatalf("missing last contact") 710 } 711 }) 712 } 713 714 func TestHTTP_JobAllocations(t *testing.T) { 715 t.Parallel() 716 httpTest(t, nil, func(s *TestAgent) { 717 // Create the job 718 alloc1 := mock.Alloc() 719 args := structs.JobRegisterRequest{ 720 Job: alloc1.Job, 721 WriteRequest: structs.WriteRequest{ 722 Region: "global", 723 Namespace: structs.DefaultNamespace, 724 }, 725 } 726 var resp structs.JobRegisterResponse 727 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 728 t.Fatalf("err: %v", err) 729 } 730 731 // Directly manipulate the state 732 expectedDisplayMsg := "test message" 733 testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg) 734 var events []*structs.TaskEvent 735 events = append(events, testEvent) 736 taskState := &structs.TaskState{Events: events} 737 alloc1.TaskStates = make(map[string]*structs.TaskState) 738 alloc1.TaskStates["test"] = taskState 739 state := s.Agent.server.State() 740 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 741 if err != nil { 742 t.Fatalf("err: %v", err) 743 } 744 745 // Make the HTTP request 746 req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil) 747 if err != nil { 748 t.Fatalf("err: %v", err) 749 } 750 respW := httptest.NewRecorder() 751 752 // Make the request 753 obj, err := s.Server.JobSpecificRequest(respW, req) 754 if err != nil { 755 t.Fatalf("err: %v", err) 756 } 757 758 // Check the response 759 allocs := obj.([]*structs.AllocListStub) 760 if len(allocs) != 1 && allocs[0].ID != alloc1.ID { 761 t.Fatalf("bad: %v", allocs) 762 } 763 displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage 764 assert.Equal(t, expectedDisplayMsg, displayMsg) 765 766 // Check for the index 767 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 768 t.Fatalf("missing index") 769 } 770 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 771 t.Fatalf("missing known leader") 772 } 773 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 774 t.Fatalf("missing last contact") 775 } 776 }) 777 } 778 779 func TestHTTP_JobDeployments(t *testing.T) { 780 assert := assert.New(t) 781 t.Parallel() 782 httpTest(t, nil, func(s *TestAgent) { 783 // Create the job 784 j := mock.Job() 785 args := structs.JobRegisterRequest{ 786 Job: j, 787 WriteRequest: structs.WriteRequest{ 788 Region: "global", 789 Namespace: structs.DefaultNamespace, 790 }, 791 } 792 var resp structs.JobRegisterResponse 793 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 794 795 // Directly manipulate the state 796 state := s.Agent.server.State() 797 d := mock.Deployment() 798 d.JobID = j.ID 799 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 800 801 // Make the HTTP request 802 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil) 803 assert.Nil(err, "HTTP") 804 respW := httptest.NewRecorder() 805 806 // Make the request 807 obj, err := s.Server.JobSpecificRequest(respW, req) 808 assert.Nil(err, "JobSpecificRequest") 809 810 // Check the response 811 deploys := obj.([]*structs.Deployment) 812 assert.Len(deploys, 1, "deployments") 813 assert.Equal(d.ID, deploys[0].ID, "deployment id") 814 815 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 816 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 817 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 818 }) 819 } 820 821 func TestHTTP_JobDeployment(t *testing.T) { 822 assert := assert.New(t) 823 t.Parallel() 824 httpTest(t, nil, func(s *TestAgent) { 825 // Create the job 826 j := mock.Job() 827 args := structs.JobRegisterRequest{ 828 Job: j, 829 WriteRequest: structs.WriteRequest{ 830 Region: "global", 831 Namespace: structs.DefaultNamespace, 832 }, 833 } 834 var resp structs.JobRegisterResponse 835 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 836 837 // Directly manipulate the state 838 state := s.Agent.server.State() 839 d := mock.Deployment() 840 d.JobID = j.ID 841 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 842 843 // Make the HTTP request 844 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil) 845 assert.Nil(err, "HTTP") 846 respW := httptest.NewRecorder() 847 848 // Make the request 849 obj, err := s.Server.JobSpecificRequest(respW, req) 850 assert.Nil(err, "JobSpecificRequest") 851 852 // Check the response 853 out := obj.(*structs.Deployment) 854 assert.NotNil(out, "deployment") 855 assert.Equal(d.ID, out.ID, "deployment id") 856 857 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 858 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 859 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 860 }) 861 } 862 863 func TestHTTP_JobVersions(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 job2 := mock.Job() 881 job2.ID = job.ID 882 job2.Priority = 100 883 884 args2 := structs.JobRegisterRequest{ 885 Job: job2, 886 WriteRequest: structs.WriteRequest{ 887 Region: "global", 888 Namespace: structs.DefaultNamespace, 889 }, 890 } 891 var resp2 structs.JobRegisterResponse 892 if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil { 893 t.Fatalf("err: %v", err) 894 } 895 896 // Make the HTTP request 897 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil) 898 if err != nil { 899 t.Fatalf("err: %v", err) 900 } 901 respW := httptest.NewRecorder() 902 903 // Make the request 904 obj, err := s.Server.JobSpecificRequest(respW, req) 905 if err != nil { 906 t.Fatalf("err: %v", err) 907 } 908 909 // Check the response 910 vResp := obj.(structs.JobVersionsResponse) 911 versions := vResp.Versions 912 if len(versions) != 2 { 913 t.Fatalf("got %d versions; want 2", len(versions)) 914 } 915 916 if v := versions[0]; v.Version != 1 || v.Priority != 100 { 917 t.Fatalf("bad %v", v) 918 } 919 920 if v := versions[1]; v.Version != 0 { 921 t.Fatalf("bad %v", v) 922 } 923 924 if len(vResp.Diffs) != 1 { 925 t.Fatalf("bad %v", vResp) 926 } 927 928 // Check for the index 929 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 930 t.Fatalf("missing index") 931 } 932 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 933 t.Fatalf("missing known leader") 934 } 935 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 936 t.Fatalf("missing last contact") 937 } 938 }) 939 } 940 941 func TestHTTP_PeriodicForce(t *testing.T) { 942 t.Parallel() 943 httpTest(t, nil, func(s *TestAgent) { 944 // Create and register a periodic job. 945 job := mock.PeriodicJob() 946 args := structs.JobRegisterRequest{ 947 Job: job, 948 WriteRequest: structs.WriteRequest{ 949 Region: "global", 950 Namespace: structs.DefaultNamespace, 951 }, 952 } 953 var resp structs.JobRegisterResponse 954 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 955 t.Fatalf("err: %v", err) 956 } 957 958 // Make the HTTP request 959 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) 960 if err != nil { 961 t.Fatalf("err: %v", err) 962 } 963 respW := httptest.NewRecorder() 964 965 // Make the request 966 obj, err := s.Server.JobSpecificRequest(respW, req) 967 if err != nil { 968 t.Fatalf("err: %v", err) 969 } 970 971 // Check for the index 972 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 973 t.Fatalf("missing index") 974 } 975 976 // Check the response 977 r := obj.(structs.PeriodicForceResponse) 978 if r.EvalID == "" { 979 t.Fatalf("bad: %#v", r) 980 } 981 }) 982 } 983 984 func TestHTTP_JobPlan(t *testing.T) { 985 t.Parallel() 986 httpTest(t, nil, func(s *TestAgent) { 987 // Create the job 988 job := api.MockJob() 989 args := api.JobPlanRequest{ 990 Job: job, 991 Diff: true, 992 WriteRequest: api.WriteRequest{ 993 Region: "global", 994 Namespace: api.DefaultNamespace, 995 }, 996 } 997 buf := encodeReq(args) 998 999 // Make the HTTP request 1000 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) 1001 if err != nil { 1002 t.Fatalf("err: %v", err) 1003 } 1004 respW := httptest.NewRecorder() 1005 1006 // Make the request 1007 obj, err := s.Server.JobSpecificRequest(respW, req) 1008 if err != nil { 1009 t.Fatalf("err: %v", err) 1010 } 1011 1012 // Check the response 1013 plan := obj.(structs.JobPlanResponse) 1014 if plan.Annotations == nil { 1015 t.Fatalf("bad: %v", plan) 1016 } 1017 1018 if plan.Diff == nil { 1019 t.Fatalf("bad: %v", plan) 1020 } 1021 }) 1022 } 1023 1024 func TestHTTP_JobDispatch(t *testing.T) { 1025 t.Parallel() 1026 httpTest(t, nil, func(s *TestAgent) { 1027 // Create the parameterized job 1028 job := mock.BatchJob() 1029 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1030 1031 args := structs.JobRegisterRequest{ 1032 Job: job, 1033 WriteRequest: structs.WriteRequest{ 1034 Region: "global", 1035 Namespace: structs.DefaultNamespace, 1036 }, 1037 } 1038 var resp structs.JobRegisterResponse 1039 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 1040 t.Fatalf("err: %v", err) 1041 } 1042 1043 // Make the request 1044 respW := httptest.NewRecorder() 1045 args2 := structs.JobDispatchRequest{ 1046 WriteRequest: structs.WriteRequest{ 1047 Region: "global", 1048 Namespace: structs.DefaultNamespace, 1049 }, 1050 } 1051 buf := encodeReq(args2) 1052 1053 // Make the HTTP request 1054 req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) 1055 if err != nil { 1056 t.Fatalf("err: %v", err) 1057 } 1058 respW.Flush() 1059 1060 // Make the request 1061 obj, err := s.Server.JobSpecificRequest(respW, req2) 1062 if err != nil { 1063 t.Fatalf("err: %v", err) 1064 } 1065 1066 // Check the response 1067 dispatch := obj.(structs.JobDispatchResponse) 1068 if dispatch.EvalID == "" { 1069 t.Fatalf("bad: %v", dispatch) 1070 } 1071 1072 if dispatch.DispatchedJobID == "" { 1073 t.Fatalf("bad: %v", dispatch) 1074 } 1075 }) 1076 } 1077 1078 func TestHTTP_JobRevert(t *testing.T) { 1079 t.Parallel() 1080 httpTest(t, nil, func(s *TestAgent) { 1081 // Create the job and register it twice 1082 job := mock.Job() 1083 regReq := structs.JobRegisterRequest{ 1084 Job: job, 1085 WriteRequest: structs.WriteRequest{ 1086 Region: "global", 1087 Namespace: structs.DefaultNamespace, 1088 }, 1089 } 1090 var regResp structs.JobRegisterResponse 1091 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1092 t.Fatalf("err: %v", err) 1093 } 1094 1095 // Change the job to get a new version 1096 job.Datacenters = append(job.Datacenters, "foo") 1097 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1098 t.Fatalf("err: %v", err) 1099 } 1100 1101 args := structs.JobRevertRequest{ 1102 JobID: job.ID, 1103 JobVersion: 0, 1104 WriteRequest: structs.WriteRequest{ 1105 Region: "global", 1106 Namespace: structs.DefaultNamespace, 1107 }, 1108 } 1109 buf := encodeReq(args) 1110 1111 // Make the HTTP request 1112 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) 1113 if err != nil { 1114 t.Fatalf("err: %v", err) 1115 } 1116 respW := httptest.NewRecorder() 1117 1118 // Make the request 1119 obj, err := s.Server.JobSpecificRequest(respW, req) 1120 if err != nil { 1121 t.Fatalf("err: %v", err) 1122 } 1123 1124 // Check the response 1125 revertResp := obj.(structs.JobRegisterResponse) 1126 if revertResp.EvalID == "" { 1127 t.Fatalf("bad: %v", revertResp) 1128 } 1129 1130 // Check for the index 1131 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1132 t.Fatalf("missing index") 1133 } 1134 }) 1135 } 1136 1137 func TestHTTP_JobStable(t *testing.T) { 1138 t.Parallel() 1139 httpTest(t, nil, func(s *TestAgent) { 1140 // Create the job and register it twice 1141 job := mock.Job() 1142 regReq := structs.JobRegisterRequest{ 1143 Job: job, 1144 WriteRequest: structs.WriteRequest{ 1145 Region: "global", 1146 Namespace: structs.DefaultNamespace, 1147 }, 1148 } 1149 var regResp structs.JobRegisterResponse 1150 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1151 t.Fatalf("err: %v", err) 1152 } 1153 1154 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1155 t.Fatalf("err: %v", err) 1156 } 1157 1158 args := structs.JobStabilityRequest{ 1159 JobID: job.ID, 1160 JobVersion: 0, 1161 Stable: true, 1162 WriteRequest: structs.WriteRequest{ 1163 Region: "global", 1164 Namespace: structs.DefaultNamespace, 1165 }, 1166 } 1167 buf := encodeReq(args) 1168 1169 // Make the HTTP request 1170 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf) 1171 if err != nil { 1172 t.Fatalf("err: %v", err) 1173 } 1174 respW := httptest.NewRecorder() 1175 1176 // Make the request 1177 obj, err := s.Server.JobSpecificRequest(respW, req) 1178 if err != nil { 1179 t.Fatalf("err: %v", err) 1180 } 1181 1182 // Check the response 1183 stableResp := obj.(structs.JobStabilityResponse) 1184 if stableResp.Index == 0 { 1185 t.Fatalf("bad: %v", stableResp) 1186 } 1187 1188 // Check for the index 1189 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1190 t.Fatalf("missing index") 1191 } 1192 }) 1193 } 1194 1195 func TestJobs_ApiJobToStructsJob(t *testing.T) { 1196 apiJob := &api.Job{ 1197 Stop: helper.BoolToPtr(true), 1198 Region: helper.StringToPtr("global"), 1199 Namespace: helper.StringToPtr("foo"), 1200 ID: helper.StringToPtr("foo"), 1201 ParentID: helper.StringToPtr("lol"), 1202 Name: helper.StringToPtr("name"), 1203 Type: helper.StringToPtr("service"), 1204 Priority: helper.IntToPtr(50), 1205 AllAtOnce: helper.BoolToPtr(true), 1206 Datacenters: []string{"dc1", "dc2"}, 1207 Constraints: []*api.Constraint{ 1208 { 1209 LTarget: "a", 1210 RTarget: "b", 1211 Operand: "c", 1212 }, 1213 }, 1214 Affinities: []*api.Affinity{ 1215 { 1216 LTarget: "a", 1217 RTarget: "b", 1218 Operand: "c", 1219 Weight: 50, 1220 }, 1221 }, 1222 Update: &api.UpdateStrategy{ 1223 Stagger: helper.TimeToPtr(1 * time.Second), 1224 MaxParallel: helper.IntToPtr(5), 1225 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 1226 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 1227 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 1228 ProgressDeadline: helper.TimeToPtr(3 * time.Minute), 1229 AutoRevert: helper.BoolToPtr(false), 1230 Canary: helper.IntToPtr(1), 1231 }, 1232 Spreads: []*api.Spread{ 1233 { 1234 Attribute: "${meta.rack}", 1235 Weight: 100, 1236 SpreadTarget: []*api.SpreadTarget{ 1237 { 1238 Value: "r1", 1239 Percent: 50, 1240 }, 1241 }, 1242 }, 1243 }, 1244 Periodic: &api.PeriodicConfig{ 1245 Enabled: helper.BoolToPtr(true), 1246 Spec: helper.StringToPtr("spec"), 1247 SpecType: helper.StringToPtr("cron"), 1248 ProhibitOverlap: helper.BoolToPtr(true), 1249 TimeZone: helper.StringToPtr("test zone"), 1250 }, 1251 ParameterizedJob: &api.ParameterizedJobConfig{ 1252 Payload: "payload", 1253 MetaRequired: []string{"a", "b"}, 1254 MetaOptional: []string{"c", "d"}, 1255 }, 1256 Payload: []byte("payload"), 1257 Meta: map[string]string{ 1258 "foo": "bar", 1259 }, 1260 TaskGroups: []*api.TaskGroup{ 1261 { 1262 Name: helper.StringToPtr("group1"), 1263 Count: helper.IntToPtr(5), 1264 Constraints: []*api.Constraint{ 1265 { 1266 LTarget: "x", 1267 RTarget: "y", 1268 Operand: "z", 1269 }, 1270 }, 1271 Affinities: []*api.Affinity{ 1272 { 1273 LTarget: "x", 1274 RTarget: "y", 1275 Operand: "z", 1276 Weight: 100, 1277 }, 1278 }, 1279 RestartPolicy: &api.RestartPolicy{ 1280 Interval: helper.TimeToPtr(1 * time.Second), 1281 Attempts: helper.IntToPtr(5), 1282 Delay: helper.TimeToPtr(10 * time.Second), 1283 Mode: helper.StringToPtr("delay"), 1284 }, 1285 ReschedulePolicy: &api.ReschedulePolicy{ 1286 Interval: helper.TimeToPtr(12 * time.Hour), 1287 Attempts: helper.IntToPtr(5), 1288 DelayFunction: helper.StringToPtr("constant"), 1289 Delay: helper.TimeToPtr(30 * time.Second), 1290 Unlimited: helper.BoolToPtr(true), 1291 MaxDelay: helper.TimeToPtr(20 * time.Minute), 1292 }, 1293 Migrate: &api.MigrateStrategy{ 1294 MaxParallel: helper.IntToPtr(12), 1295 HealthCheck: helper.StringToPtr("task_events"), 1296 MinHealthyTime: helper.TimeToPtr(12 * time.Hour), 1297 HealthyDeadline: helper.TimeToPtr(12 * time.Hour), 1298 }, 1299 Spreads: []*api.Spread{ 1300 { 1301 Attribute: "${node.datacenter}", 1302 Weight: 100, 1303 SpreadTarget: []*api.SpreadTarget{ 1304 { 1305 Value: "dc1", 1306 Percent: 100, 1307 }, 1308 }, 1309 }, 1310 }, 1311 EphemeralDisk: &api.EphemeralDisk{ 1312 SizeMB: helper.IntToPtr(100), 1313 Sticky: helper.BoolToPtr(true), 1314 Migrate: helper.BoolToPtr(true), 1315 }, 1316 Update: &api.UpdateStrategy{ 1317 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), 1318 MinHealthyTime: helper.TimeToPtr(2 * time.Minute), 1319 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 1320 ProgressDeadline: helper.TimeToPtr(5 * time.Minute), 1321 AutoRevert: helper.BoolToPtr(true), 1322 }, 1323 1324 Meta: map[string]string{ 1325 "key": "value", 1326 }, 1327 Tasks: []*api.Task{ 1328 { 1329 Name: "task1", 1330 Leader: true, 1331 Driver: "docker", 1332 User: "mary", 1333 Config: map[string]interface{}{ 1334 "lol": "code", 1335 }, 1336 Env: map[string]string{ 1337 "hello": "world", 1338 }, 1339 Constraints: []*api.Constraint{ 1340 { 1341 LTarget: "x", 1342 RTarget: "y", 1343 Operand: "z", 1344 }, 1345 }, 1346 Affinities: []*api.Affinity{ 1347 { 1348 LTarget: "a", 1349 RTarget: "b", 1350 Operand: "c", 1351 Weight: 50, 1352 }, 1353 }, 1354 1355 Services: []*api.Service{ 1356 { 1357 Id: "id", 1358 Name: "serviceA", 1359 Tags: []string{"1", "2"}, 1360 CanaryTags: []string{"3", "4"}, 1361 PortLabel: "foo", 1362 CheckRestart: &api.CheckRestart{ 1363 Limit: 4, 1364 Grace: helper.TimeToPtr(11 * time.Second), 1365 }, 1366 Checks: []api.ServiceCheck{ 1367 { 1368 Id: "hello", 1369 Name: "bar", 1370 Type: "http", 1371 Command: "foo", 1372 Args: []string{"a", "b"}, 1373 Path: "/check", 1374 Protocol: "http", 1375 PortLabel: "foo", 1376 AddressMode: "driver", 1377 GRPCService: "foo.Bar", 1378 GRPCUseTLS: true, 1379 Interval: 4 * time.Second, 1380 Timeout: 2 * time.Second, 1381 InitialStatus: "ok", 1382 CheckRestart: &api.CheckRestart{ 1383 Limit: 3, 1384 IgnoreWarnings: true, 1385 }, 1386 }, 1387 { 1388 Id: "check2id", 1389 Name: "check2", 1390 Type: "tcp", 1391 PortLabel: "foo", 1392 Interval: 4 * time.Second, 1393 Timeout: 2 * time.Second, 1394 }, 1395 }, 1396 }, 1397 }, 1398 Resources: &api.Resources{ 1399 CPU: helper.IntToPtr(100), 1400 MemoryMB: helper.IntToPtr(10), 1401 Networks: []*api.NetworkResource{ 1402 { 1403 IP: "10.10.11.1", 1404 MBits: helper.IntToPtr(10), 1405 ReservedPorts: []api.Port{ 1406 { 1407 Label: "http", 1408 Value: 80, 1409 }, 1410 }, 1411 DynamicPorts: []api.Port{ 1412 { 1413 Label: "ssh", 1414 Value: 2000, 1415 }, 1416 }, 1417 }, 1418 }, 1419 }, 1420 Meta: map[string]string{ 1421 "lol": "code", 1422 }, 1423 KillTimeout: helper.TimeToPtr(10 * time.Second), 1424 KillSignal: "SIGQUIT", 1425 LogConfig: &api.LogConfig{ 1426 MaxFiles: helper.IntToPtr(10), 1427 MaxFileSizeMB: helper.IntToPtr(100), 1428 }, 1429 Artifacts: []*api.TaskArtifact{ 1430 { 1431 GetterSource: helper.StringToPtr("source"), 1432 GetterOptions: map[string]string{ 1433 "a": "b", 1434 }, 1435 GetterMode: helper.StringToPtr("dir"), 1436 RelativeDest: helper.StringToPtr("dest"), 1437 }, 1438 }, 1439 Vault: &api.Vault{ 1440 Policies: []string{"a", "b", "c"}, 1441 Env: helper.BoolToPtr(true), 1442 ChangeMode: helper.StringToPtr("c"), 1443 ChangeSignal: helper.StringToPtr("sighup"), 1444 }, 1445 Templates: []*api.Template{ 1446 { 1447 SourcePath: helper.StringToPtr("source"), 1448 DestPath: helper.StringToPtr("dest"), 1449 EmbeddedTmpl: helper.StringToPtr("embedded"), 1450 ChangeMode: helper.StringToPtr("change"), 1451 ChangeSignal: helper.StringToPtr("signal"), 1452 Splay: helper.TimeToPtr(1 * time.Minute), 1453 Perms: helper.StringToPtr("666"), 1454 LeftDelim: helper.StringToPtr("abc"), 1455 RightDelim: helper.StringToPtr("def"), 1456 Envvars: helper.BoolToPtr(true), 1457 VaultGrace: helper.TimeToPtr(3 * time.Second), 1458 }, 1459 }, 1460 DispatchPayload: &api.DispatchPayloadConfig{ 1461 File: "fileA", 1462 }, 1463 }, 1464 }, 1465 }, 1466 }, 1467 VaultToken: helper.StringToPtr("token"), 1468 Status: helper.StringToPtr("status"), 1469 StatusDescription: helper.StringToPtr("status_desc"), 1470 Version: helper.Uint64ToPtr(10), 1471 CreateIndex: helper.Uint64ToPtr(1), 1472 ModifyIndex: helper.Uint64ToPtr(3), 1473 JobModifyIndex: helper.Uint64ToPtr(5), 1474 } 1475 1476 expected := &structs.Job{ 1477 Stop: true, 1478 Region: "global", 1479 Namespace: "foo", 1480 ID: "foo", 1481 ParentID: "lol", 1482 Name: "name", 1483 Type: "service", 1484 Priority: 50, 1485 AllAtOnce: true, 1486 Datacenters: []string{"dc1", "dc2"}, 1487 Constraints: []*structs.Constraint{ 1488 { 1489 LTarget: "a", 1490 RTarget: "b", 1491 Operand: "c", 1492 }, 1493 }, 1494 Affinities: []*structs.Affinity{ 1495 { 1496 LTarget: "a", 1497 RTarget: "b", 1498 Operand: "c", 1499 Weight: 50, 1500 }, 1501 }, 1502 Spreads: []*structs.Spread{ 1503 { 1504 Attribute: "${meta.rack}", 1505 Weight: 100, 1506 SpreadTarget: []*structs.SpreadTarget{ 1507 { 1508 Value: "r1", 1509 Percent: 50, 1510 }, 1511 }, 1512 }, 1513 }, 1514 Update: structs.UpdateStrategy{ 1515 Stagger: 1 * time.Second, 1516 MaxParallel: 5, 1517 }, 1518 Periodic: &structs.PeriodicConfig{ 1519 Enabled: true, 1520 Spec: "spec", 1521 SpecType: "cron", 1522 ProhibitOverlap: true, 1523 TimeZone: "test zone", 1524 }, 1525 ParameterizedJob: &structs.ParameterizedJobConfig{ 1526 Payload: "payload", 1527 MetaRequired: []string{"a", "b"}, 1528 MetaOptional: []string{"c", "d"}, 1529 }, 1530 Payload: []byte("payload"), 1531 Meta: map[string]string{ 1532 "foo": "bar", 1533 }, 1534 TaskGroups: []*structs.TaskGroup{ 1535 { 1536 Name: "group1", 1537 Count: 5, 1538 Constraints: []*structs.Constraint{ 1539 { 1540 LTarget: "x", 1541 RTarget: "y", 1542 Operand: "z", 1543 }, 1544 }, 1545 Affinities: []*structs.Affinity{ 1546 { 1547 LTarget: "x", 1548 RTarget: "y", 1549 Operand: "z", 1550 Weight: 100, 1551 }, 1552 }, 1553 RestartPolicy: &structs.RestartPolicy{ 1554 Interval: 1 * time.Second, 1555 Attempts: 5, 1556 Delay: 10 * time.Second, 1557 Mode: "delay", 1558 }, 1559 Spreads: []*structs.Spread{ 1560 { 1561 Attribute: "${node.datacenter}", 1562 Weight: 100, 1563 SpreadTarget: []*structs.SpreadTarget{ 1564 { 1565 Value: "dc1", 1566 Percent: 100, 1567 }, 1568 }, 1569 }, 1570 }, 1571 ReschedulePolicy: &structs.ReschedulePolicy{ 1572 Interval: 12 * time.Hour, 1573 Attempts: 5, 1574 DelayFunction: "constant", 1575 Delay: 30 * time.Second, 1576 Unlimited: true, 1577 MaxDelay: 20 * time.Minute, 1578 }, 1579 Migrate: &structs.MigrateStrategy{ 1580 MaxParallel: 12, 1581 HealthCheck: "task_events", 1582 MinHealthyTime: 12 * time.Hour, 1583 HealthyDeadline: 12 * time.Hour, 1584 }, 1585 EphemeralDisk: &structs.EphemeralDisk{ 1586 SizeMB: 100, 1587 Sticky: true, 1588 Migrate: true, 1589 }, 1590 Update: &structs.UpdateStrategy{ 1591 Stagger: 1 * time.Second, 1592 MaxParallel: 5, 1593 HealthCheck: structs.UpdateStrategyHealthCheck_Checks, 1594 MinHealthyTime: 2 * time.Minute, 1595 HealthyDeadline: 5 * time.Minute, 1596 ProgressDeadline: 5 * time.Minute, 1597 AutoRevert: true, 1598 Canary: 1, 1599 }, 1600 Meta: map[string]string{ 1601 "key": "value", 1602 }, 1603 Tasks: []*structs.Task{ 1604 { 1605 Name: "task1", 1606 Driver: "docker", 1607 Leader: true, 1608 User: "mary", 1609 Config: map[string]interface{}{ 1610 "lol": "code", 1611 }, 1612 Constraints: []*structs.Constraint{ 1613 { 1614 LTarget: "x", 1615 RTarget: "y", 1616 Operand: "z", 1617 }, 1618 }, 1619 Affinities: []*structs.Affinity{ 1620 { 1621 LTarget: "a", 1622 RTarget: "b", 1623 Operand: "c", 1624 Weight: 50, 1625 }, 1626 }, 1627 Env: map[string]string{ 1628 "hello": "world", 1629 }, 1630 Services: []*structs.Service{ 1631 { 1632 Name: "serviceA", 1633 Tags: []string{"1", "2"}, 1634 CanaryTags: []string{"3", "4"}, 1635 PortLabel: "foo", 1636 AddressMode: "auto", 1637 Checks: []*structs.ServiceCheck{ 1638 { 1639 Name: "bar", 1640 Type: "http", 1641 Command: "foo", 1642 Args: []string{"a", "b"}, 1643 Path: "/check", 1644 Protocol: "http", 1645 PortLabel: "foo", 1646 AddressMode: "driver", 1647 Interval: 4 * time.Second, 1648 Timeout: 2 * time.Second, 1649 InitialStatus: "ok", 1650 GRPCService: "foo.Bar", 1651 GRPCUseTLS: true, 1652 CheckRestart: &structs.CheckRestart{ 1653 Limit: 3, 1654 Grace: 11 * time.Second, 1655 IgnoreWarnings: true, 1656 }, 1657 }, 1658 { 1659 Name: "check2", 1660 Type: "tcp", 1661 PortLabel: "foo", 1662 Interval: 4 * time.Second, 1663 Timeout: 2 * time.Second, 1664 CheckRestart: &structs.CheckRestart{ 1665 Limit: 4, 1666 Grace: 11 * time.Second, 1667 }, 1668 }, 1669 }, 1670 }, 1671 }, 1672 Resources: &structs.Resources{ 1673 CPU: 100, 1674 MemoryMB: 10, 1675 Networks: []*structs.NetworkResource{ 1676 { 1677 IP: "10.10.11.1", 1678 MBits: 10, 1679 ReservedPorts: []structs.Port{ 1680 { 1681 Label: "http", 1682 Value: 80, 1683 }, 1684 }, 1685 DynamicPorts: []structs.Port{ 1686 { 1687 Label: "ssh", 1688 Value: 2000, 1689 }, 1690 }, 1691 }, 1692 }, 1693 }, 1694 Meta: map[string]string{ 1695 "lol": "code", 1696 }, 1697 KillTimeout: 10 * time.Second, 1698 KillSignal: "SIGQUIT", 1699 LogConfig: &structs.LogConfig{ 1700 MaxFiles: 10, 1701 MaxFileSizeMB: 100, 1702 }, 1703 Artifacts: []*structs.TaskArtifact{ 1704 { 1705 GetterSource: "source", 1706 GetterOptions: map[string]string{ 1707 "a": "b", 1708 }, 1709 GetterMode: "dir", 1710 RelativeDest: "dest", 1711 }, 1712 }, 1713 Vault: &structs.Vault{ 1714 Policies: []string{"a", "b", "c"}, 1715 Env: true, 1716 ChangeMode: "c", 1717 ChangeSignal: "sighup", 1718 }, 1719 Templates: []*structs.Template{ 1720 { 1721 SourcePath: "source", 1722 DestPath: "dest", 1723 EmbeddedTmpl: "embedded", 1724 ChangeMode: "change", 1725 ChangeSignal: "SIGNAL", 1726 Splay: 1 * time.Minute, 1727 Perms: "666", 1728 LeftDelim: "abc", 1729 RightDelim: "def", 1730 Envvars: true, 1731 VaultGrace: 3 * time.Second, 1732 }, 1733 }, 1734 DispatchPayload: &structs.DispatchPayloadConfig{ 1735 File: "fileA", 1736 }, 1737 }, 1738 }, 1739 }, 1740 }, 1741 1742 VaultToken: "token", 1743 } 1744 1745 structsJob := ApiJobToStructJob(apiJob) 1746 1747 if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { 1748 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1749 } 1750 1751 systemAPIJob := &api.Job{ 1752 Stop: helper.BoolToPtr(true), 1753 Region: helper.StringToPtr("global"), 1754 Namespace: helper.StringToPtr("foo"), 1755 ID: helper.StringToPtr("foo"), 1756 ParentID: helper.StringToPtr("lol"), 1757 Name: helper.StringToPtr("name"), 1758 Type: helper.StringToPtr("system"), 1759 Priority: helper.IntToPtr(50), 1760 AllAtOnce: helper.BoolToPtr(true), 1761 Datacenters: []string{"dc1", "dc2"}, 1762 Constraints: []*api.Constraint{ 1763 { 1764 LTarget: "a", 1765 RTarget: "b", 1766 Operand: "c", 1767 }, 1768 }, 1769 TaskGroups: []*api.TaskGroup{ 1770 { 1771 Name: helper.StringToPtr("group1"), 1772 Count: helper.IntToPtr(5), 1773 Constraints: []*api.Constraint{ 1774 { 1775 LTarget: "x", 1776 RTarget: "y", 1777 Operand: "z", 1778 }, 1779 }, 1780 RestartPolicy: &api.RestartPolicy{ 1781 Interval: helper.TimeToPtr(1 * time.Second), 1782 Attempts: helper.IntToPtr(5), 1783 Delay: helper.TimeToPtr(10 * time.Second), 1784 Mode: helper.StringToPtr("delay"), 1785 }, 1786 EphemeralDisk: &api.EphemeralDisk{ 1787 SizeMB: helper.IntToPtr(100), 1788 Sticky: helper.BoolToPtr(true), 1789 Migrate: helper.BoolToPtr(true), 1790 }, 1791 Meta: map[string]string{ 1792 "key": "value", 1793 }, 1794 Tasks: []*api.Task{ 1795 { 1796 Name: "task1", 1797 Leader: true, 1798 Driver: "docker", 1799 User: "mary", 1800 Config: map[string]interface{}{ 1801 "lol": "code", 1802 }, 1803 Env: map[string]string{ 1804 "hello": "world", 1805 }, 1806 Constraints: []*api.Constraint{ 1807 { 1808 LTarget: "x", 1809 RTarget: "y", 1810 Operand: "z", 1811 }, 1812 }, 1813 Resources: &api.Resources{ 1814 CPU: helper.IntToPtr(100), 1815 MemoryMB: helper.IntToPtr(10), 1816 Networks: []*api.NetworkResource{ 1817 { 1818 IP: "10.10.11.1", 1819 MBits: helper.IntToPtr(10), 1820 ReservedPorts: []api.Port{ 1821 { 1822 Label: "http", 1823 Value: 80, 1824 }, 1825 }, 1826 DynamicPorts: []api.Port{ 1827 { 1828 Label: "ssh", 1829 Value: 2000, 1830 }, 1831 }, 1832 }, 1833 }, 1834 }, 1835 Meta: map[string]string{ 1836 "lol": "code", 1837 }, 1838 KillTimeout: helper.TimeToPtr(10 * time.Second), 1839 KillSignal: "SIGQUIT", 1840 LogConfig: &api.LogConfig{ 1841 MaxFiles: helper.IntToPtr(10), 1842 MaxFileSizeMB: helper.IntToPtr(100), 1843 }, 1844 Artifacts: []*api.TaskArtifact{ 1845 { 1846 GetterSource: helper.StringToPtr("source"), 1847 GetterOptions: map[string]string{ 1848 "a": "b", 1849 }, 1850 GetterMode: helper.StringToPtr("dir"), 1851 RelativeDest: helper.StringToPtr("dest"), 1852 }, 1853 }, 1854 DispatchPayload: &api.DispatchPayloadConfig{ 1855 File: "fileA", 1856 }, 1857 }, 1858 }, 1859 }, 1860 }, 1861 Status: helper.StringToPtr("status"), 1862 StatusDescription: helper.StringToPtr("status_desc"), 1863 Version: helper.Uint64ToPtr(10), 1864 CreateIndex: helper.Uint64ToPtr(1), 1865 ModifyIndex: helper.Uint64ToPtr(3), 1866 JobModifyIndex: helper.Uint64ToPtr(5), 1867 } 1868 1869 expectedSystemJob := &structs.Job{ 1870 Stop: true, 1871 Region: "global", 1872 Namespace: "foo", 1873 ID: "foo", 1874 ParentID: "lol", 1875 Name: "name", 1876 Type: "system", 1877 Priority: 50, 1878 AllAtOnce: true, 1879 Datacenters: []string{"dc1", "dc2"}, 1880 Constraints: []*structs.Constraint{ 1881 { 1882 LTarget: "a", 1883 RTarget: "b", 1884 Operand: "c", 1885 }, 1886 }, 1887 TaskGroups: []*structs.TaskGroup{ 1888 { 1889 Name: "group1", 1890 Count: 5, 1891 Constraints: []*structs.Constraint{ 1892 { 1893 LTarget: "x", 1894 RTarget: "y", 1895 Operand: "z", 1896 }, 1897 }, 1898 RestartPolicy: &structs.RestartPolicy{ 1899 Interval: 1 * time.Second, 1900 Attempts: 5, 1901 Delay: 10 * time.Second, 1902 Mode: "delay", 1903 }, 1904 EphemeralDisk: &structs.EphemeralDisk{ 1905 SizeMB: 100, 1906 Sticky: true, 1907 Migrate: true, 1908 }, 1909 Meta: map[string]string{ 1910 "key": "value", 1911 }, 1912 Tasks: []*structs.Task{ 1913 { 1914 Name: "task1", 1915 Driver: "docker", 1916 Leader: true, 1917 User: "mary", 1918 Config: map[string]interface{}{ 1919 "lol": "code", 1920 }, 1921 Constraints: []*structs.Constraint{ 1922 { 1923 LTarget: "x", 1924 RTarget: "y", 1925 Operand: "z", 1926 }, 1927 }, 1928 Env: map[string]string{ 1929 "hello": "world", 1930 }, 1931 Resources: &structs.Resources{ 1932 CPU: 100, 1933 MemoryMB: 10, 1934 Networks: []*structs.NetworkResource{ 1935 { 1936 IP: "10.10.11.1", 1937 MBits: 10, 1938 ReservedPorts: []structs.Port{ 1939 { 1940 Label: "http", 1941 Value: 80, 1942 }, 1943 }, 1944 DynamicPorts: []structs.Port{ 1945 { 1946 Label: "ssh", 1947 Value: 2000, 1948 }, 1949 }, 1950 }, 1951 }, 1952 }, 1953 Meta: map[string]string{ 1954 "lol": "code", 1955 }, 1956 KillTimeout: 10 * time.Second, 1957 KillSignal: "SIGQUIT", 1958 LogConfig: &structs.LogConfig{ 1959 MaxFiles: 10, 1960 MaxFileSizeMB: 100, 1961 }, 1962 Artifacts: []*structs.TaskArtifact{ 1963 { 1964 GetterSource: "source", 1965 GetterOptions: map[string]string{ 1966 "a": "b", 1967 }, 1968 GetterMode: "dir", 1969 RelativeDest: "dest", 1970 }, 1971 }, 1972 DispatchPayload: &structs.DispatchPayloadConfig{ 1973 File: "fileA", 1974 }, 1975 }, 1976 }, 1977 }, 1978 }, 1979 } 1980 1981 systemStructsJob := ApiJobToStructJob(systemAPIJob) 1982 1983 if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 { 1984 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1985 } 1986 }