github.com/smintz/nomad@v0.8.3/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_JobEvaluations(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 629 // Make the HTTP request 630 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil) 631 if err != nil { 632 t.Fatalf("err: %v", err) 633 } 634 respW := httptest.NewRecorder() 635 636 // Make the request 637 obj, err := s.Server.JobSpecificRequest(respW, req) 638 if err != nil { 639 t.Fatalf("err: %v", err) 640 } 641 642 // Check the response 643 evals := obj.([]*structs.Evaluation) 644 // Can be multiple evals, use the last one, since they are in order 645 idx := len(evals) - 1 646 if len(evals) < 0 || evals[idx].ID != resp.EvalID { 647 t.Fatalf("bad: %v", evals) 648 } 649 650 // Check for the index 651 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 652 t.Fatalf("missing index") 653 } 654 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 655 t.Fatalf("missing known leader") 656 } 657 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 658 t.Fatalf("missing last contact") 659 } 660 }) 661 } 662 663 func TestHTTP_JobAllocations(t *testing.T) { 664 t.Parallel() 665 httpTest(t, nil, func(s *TestAgent) { 666 // Create the job 667 alloc1 := mock.Alloc() 668 args := structs.JobRegisterRequest{ 669 Job: alloc1.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 // Directly manipulate the state 681 expectedDisplayMsg := "test message" 682 testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg) 683 var events []*structs.TaskEvent 684 events = append(events, testEvent) 685 taskState := &structs.TaskState{Events: events} 686 alloc1.TaskStates = make(map[string]*structs.TaskState) 687 alloc1.TaskStates["test"] = taskState 688 state := s.Agent.server.State() 689 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 690 if err != nil { 691 t.Fatalf("err: %v", err) 692 } 693 694 // Make the HTTP request 695 req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil) 696 if err != nil { 697 t.Fatalf("err: %v", err) 698 } 699 respW := httptest.NewRecorder() 700 701 // Make the request 702 obj, err := s.Server.JobSpecificRequest(respW, req) 703 if err != nil { 704 t.Fatalf("err: %v", err) 705 } 706 707 // Check the response 708 allocs := obj.([]*structs.AllocListStub) 709 if len(allocs) != 1 && allocs[0].ID != alloc1.ID { 710 t.Fatalf("bad: %v", allocs) 711 } 712 displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage 713 assert.Equal(t, expectedDisplayMsg, displayMsg) 714 715 // Check for the index 716 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 717 t.Fatalf("missing index") 718 } 719 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 720 t.Fatalf("missing known leader") 721 } 722 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 723 t.Fatalf("missing last contact") 724 } 725 }) 726 } 727 728 func TestHTTP_JobDeployments(t *testing.T) { 729 assert := assert.New(t) 730 t.Parallel() 731 httpTest(t, nil, func(s *TestAgent) { 732 // Create the job 733 j := mock.Job() 734 args := structs.JobRegisterRequest{ 735 Job: j, 736 WriteRequest: structs.WriteRequest{ 737 Region: "global", 738 Namespace: structs.DefaultNamespace, 739 }, 740 } 741 var resp structs.JobRegisterResponse 742 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 743 744 // Directly manipulate the state 745 state := s.Agent.server.State() 746 d := mock.Deployment() 747 d.JobID = j.ID 748 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 749 750 // Make the HTTP request 751 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil) 752 assert.Nil(err, "HTTP") 753 respW := httptest.NewRecorder() 754 755 // Make the request 756 obj, err := s.Server.JobSpecificRequest(respW, req) 757 assert.Nil(err, "JobSpecificRequest") 758 759 // Check the response 760 deploys := obj.([]*structs.Deployment) 761 assert.Len(deploys, 1, "deployments") 762 assert.Equal(d.ID, deploys[0].ID, "deployment id") 763 764 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 765 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 766 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 767 }) 768 } 769 770 func TestHTTP_JobDeployment(t *testing.T) { 771 assert := assert.New(t) 772 t.Parallel() 773 httpTest(t, nil, func(s *TestAgent) { 774 // Create the job 775 j := mock.Job() 776 args := structs.JobRegisterRequest{ 777 Job: j, 778 WriteRequest: structs.WriteRequest{ 779 Region: "global", 780 Namespace: structs.DefaultNamespace, 781 }, 782 } 783 var resp structs.JobRegisterResponse 784 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 785 786 // Directly manipulate the state 787 state := s.Agent.server.State() 788 d := mock.Deployment() 789 d.JobID = j.ID 790 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 791 792 // Make the HTTP request 793 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil) 794 assert.Nil(err, "HTTP") 795 respW := httptest.NewRecorder() 796 797 // Make the request 798 obj, err := s.Server.JobSpecificRequest(respW, req) 799 assert.Nil(err, "JobSpecificRequest") 800 801 // Check the response 802 out := obj.(*structs.Deployment) 803 assert.NotNil(out, "deployment") 804 assert.Equal(d.ID, out.ID, "deployment id") 805 806 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 807 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 808 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 809 }) 810 } 811 812 func TestHTTP_JobVersions(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 829 job2 := mock.Job() 830 job2.ID = job.ID 831 job2.Priority = 100 832 833 args2 := structs.JobRegisterRequest{ 834 Job: job2, 835 WriteRequest: structs.WriteRequest{ 836 Region: "global", 837 Namespace: structs.DefaultNamespace, 838 }, 839 } 840 var resp2 structs.JobRegisterResponse 841 if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil { 842 t.Fatalf("err: %v", err) 843 } 844 845 // Make the HTTP request 846 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil) 847 if err != nil { 848 t.Fatalf("err: %v", err) 849 } 850 respW := httptest.NewRecorder() 851 852 // Make the request 853 obj, err := s.Server.JobSpecificRequest(respW, req) 854 if err != nil { 855 t.Fatalf("err: %v", err) 856 } 857 858 // Check the response 859 vResp := obj.(structs.JobVersionsResponse) 860 versions := vResp.Versions 861 if len(versions) != 2 { 862 t.Fatalf("got %d versions; want 2", len(versions)) 863 } 864 865 if v := versions[0]; v.Version != 1 || v.Priority != 100 { 866 t.Fatalf("bad %v", v) 867 } 868 869 if v := versions[1]; v.Version != 0 { 870 t.Fatalf("bad %v", v) 871 } 872 873 if len(vResp.Diffs) != 1 { 874 t.Fatalf("bad %v", vResp) 875 } 876 877 // Check for the index 878 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 879 t.Fatalf("missing index") 880 } 881 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 882 t.Fatalf("missing known leader") 883 } 884 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 885 t.Fatalf("missing last contact") 886 } 887 }) 888 } 889 890 func TestHTTP_PeriodicForce(t *testing.T) { 891 t.Parallel() 892 httpTest(t, nil, func(s *TestAgent) { 893 // Create and register a periodic job. 894 job := mock.PeriodicJob() 895 args := structs.JobRegisterRequest{ 896 Job: job, 897 WriteRequest: structs.WriteRequest{ 898 Region: "global", 899 Namespace: structs.DefaultNamespace, 900 }, 901 } 902 var resp structs.JobRegisterResponse 903 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 904 t.Fatalf("err: %v", err) 905 } 906 907 // Make the HTTP request 908 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) 909 if err != nil { 910 t.Fatalf("err: %v", err) 911 } 912 respW := httptest.NewRecorder() 913 914 // Make the request 915 obj, err := s.Server.JobSpecificRequest(respW, req) 916 if err != nil { 917 t.Fatalf("err: %v", err) 918 } 919 920 // Check for the index 921 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 922 t.Fatalf("missing index") 923 } 924 925 // Check the response 926 r := obj.(structs.PeriodicForceResponse) 927 if r.EvalID == "" { 928 t.Fatalf("bad: %#v", r) 929 } 930 }) 931 } 932 933 func TestHTTP_JobPlan(t *testing.T) { 934 t.Parallel() 935 httpTest(t, nil, func(s *TestAgent) { 936 // Create the job 937 job := api.MockJob() 938 args := api.JobPlanRequest{ 939 Job: job, 940 Diff: true, 941 WriteRequest: api.WriteRequest{ 942 Region: "global", 943 Namespace: api.DefaultNamespace, 944 }, 945 } 946 buf := encodeReq(args) 947 948 // Make the HTTP request 949 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) 950 if err != nil { 951 t.Fatalf("err: %v", err) 952 } 953 respW := httptest.NewRecorder() 954 955 // Make the request 956 obj, err := s.Server.JobSpecificRequest(respW, req) 957 if err != nil { 958 t.Fatalf("err: %v", err) 959 } 960 961 // Check the response 962 plan := obj.(structs.JobPlanResponse) 963 if plan.Annotations == nil { 964 t.Fatalf("bad: %v", plan) 965 } 966 967 if plan.Diff == nil { 968 t.Fatalf("bad: %v", plan) 969 } 970 }) 971 } 972 973 func TestHTTP_JobDispatch(t *testing.T) { 974 t.Parallel() 975 httpTest(t, nil, func(s *TestAgent) { 976 // Create the parameterized job 977 job := mock.BatchJob() 978 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 979 980 args := structs.JobRegisterRequest{ 981 Job: job, 982 WriteRequest: structs.WriteRequest{ 983 Region: "global", 984 Namespace: structs.DefaultNamespace, 985 }, 986 } 987 var resp structs.JobRegisterResponse 988 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 989 t.Fatalf("err: %v", err) 990 } 991 992 // Make the request 993 respW := httptest.NewRecorder() 994 args2 := structs.JobDispatchRequest{ 995 WriteRequest: structs.WriteRequest{ 996 Region: "global", 997 Namespace: structs.DefaultNamespace, 998 }, 999 } 1000 buf := encodeReq(args2) 1001 1002 // Make the HTTP request 1003 req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) 1004 if err != nil { 1005 t.Fatalf("err: %v", err) 1006 } 1007 respW.Flush() 1008 1009 // Make the request 1010 obj, err := s.Server.JobSpecificRequest(respW, req2) 1011 if err != nil { 1012 t.Fatalf("err: %v", err) 1013 } 1014 1015 // Check the response 1016 dispatch := obj.(structs.JobDispatchResponse) 1017 if dispatch.EvalID == "" { 1018 t.Fatalf("bad: %v", dispatch) 1019 } 1020 1021 if dispatch.DispatchedJobID == "" { 1022 t.Fatalf("bad: %v", dispatch) 1023 } 1024 }) 1025 } 1026 1027 func TestHTTP_JobRevert(t *testing.T) { 1028 t.Parallel() 1029 httpTest(t, nil, func(s *TestAgent) { 1030 // Create the job and register it twice 1031 job := mock.Job() 1032 regReq := structs.JobRegisterRequest{ 1033 Job: job, 1034 WriteRequest: structs.WriteRequest{ 1035 Region: "global", 1036 Namespace: structs.DefaultNamespace, 1037 }, 1038 } 1039 var regResp structs.JobRegisterResponse 1040 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1041 t.Fatalf("err: %v", err) 1042 } 1043 1044 // Change the job to get a new version 1045 job.Datacenters = append(job.Datacenters, "foo") 1046 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1047 t.Fatalf("err: %v", err) 1048 } 1049 1050 args := structs.JobRevertRequest{ 1051 JobID: job.ID, 1052 JobVersion: 0, 1053 WriteRequest: structs.WriteRequest{ 1054 Region: "global", 1055 Namespace: structs.DefaultNamespace, 1056 }, 1057 } 1058 buf := encodeReq(args) 1059 1060 // Make the HTTP request 1061 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) 1062 if err != nil { 1063 t.Fatalf("err: %v", err) 1064 } 1065 respW := httptest.NewRecorder() 1066 1067 // Make the request 1068 obj, err := s.Server.JobSpecificRequest(respW, req) 1069 if err != nil { 1070 t.Fatalf("err: %v", err) 1071 } 1072 1073 // Check the response 1074 revertResp := obj.(structs.JobRegisterResponse) 1075 if revertResp.EvalID == "" { 1076 t.Fatalf("bad: %v", revertResp) 1077 } 1078 1079 // Check for the index 1080 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1081 t.Fatalf("missing index") 1082 } 1083 }) 1084 } 1085 1086 func TestHTTP_JobStable(t *testing.T) { 1087 t.Parallel() 1088 httpTest(t, nil, func(s *TestAgent) { 1089 // Create the job and register it twice 1090 job := mock.Job() 1091 regReq := structs.JobRegisterRequest{ 1092 Job: job, 1093 WriteRequest: structs.WriteRequest{ 1094 Region: "global", 1095 Namespace: structs.DefaultNamespace, 1096 }, 1097 } 1098 var regResp structs.JobRegisterResponse 1099 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1100 t.Fatalf("err: %v", err) 1101 } 1102 1103 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1104 t.Fatalf("err: %v", err) 1105 } 1106 1107 args := structs.JobStabilityRequest{ 1108 JobID: job.ID, 1109 JobVersion: 0, 1110 Stable: true, 1111 WriteRequest: structs.WriteRequest{ 1112 Region: "global", 1113 Namespace: structs.DefaultNamespace, 1114 }, 1115 } 1116 buf := encodeReq(args) 1117 1118 // Make the HTTP request 1119 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf) 1120 if err != nil { 1121 t.Fatalf("err: %v", err) 1122 } 1123 respW := httptest.NewRecorder() 1124 1125 // Make the request 1126 obj, err := s.Server.JobSpecificRequest(respW, req) 1127 if err != nil { 1128 t.Fatalf("err: %v", err) 1129 } 1130 1131 // Check the response 1132 stableResp := obj.(structs.JobStabilityResponse) 1133 if stableResp.Index == 0 { 1134 t.Fatalf("bad: %v", stableResp) 1135 } 1136 1137 // Check for the index 1138 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1139 t.Fatalf("missing index") 1140 } 1141 }) 1142 } 1143 1144 func TestJobs_ApiJobToStructsJob(t *testing.T) { 1145 apiJob := &api.Job{ 1146 Stop: helper.BoolToPtr(true), 1147 Region: helper.StringToPtr("global"), 1148 Namespace: helper.StringToPtr("foo"), 1149 ID: helper.StringToPtr("foo"), 1150 ParentID: helper.StringToPtr("lol"), 1151 Name: helper.StringToPtr("name"), 1152 Type: helper.StringToPtr("service"), 1153 Priority: helper.IntToPtr(50), 1154 AllAtOnce: helper.BoolToPtr(true), 1155 Datacenters: []string{"dc1", "dc2"}, 1156 Constraints: []*api.Constraint{ 1157 { 1158 LTarget: "a", 1159 RTarget: "b", 1160 Operand: "c", 1161 }, 1162 }, 1163 Update: &api.UpdateStrategy{ 1164 Stagger: helper.TimeToPtr(1 * time.Second), 1165 MaxParallel: helper.IntToPtr(5), 1166 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 1167 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 1168 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 1169 AutoRevert: helper.BoolToPtr(false), 1170 Canary: helper.IntToPtr(1), 1171 }, 1172 Periodic: &api.PeriodicConfig{ 1173 Enabled: helper.BoolToPtr(true), 1174 Spec: helper.StringToPtr("spec"), 1175 SpecType: helper.StringToPtr("cron"), 1176 ProhibitOverlap: helper.BoolToPtr(true), 1177 TimeZone: helper.StringToPtr("test zone"), 1178 }, 1179 ParameterizedJob: &api.ParameterizedJobConfig{ 1180 Payload: "payload", 1181 MetaRequired: []string{"a", "b"}, 1182 MetaOptional: []string{"c", "d"}, 1183 }, 1184 Payload: []byte("payload"), 1185 Meta: map[string]string{ 1186 "foo": "bar", 1187 }, 1188 TaskGroups: []*api.TaskGroup{ 1189 { 1190 Name: helper.StringToPtr("group1"), 1191 Count: helper.IntToPtr(5), 1192 Constraints: []*api.Constraint{ 1193 { 1194 LTarget: "x", 1195 RTarget: "y", 1196 Operand: "z", 1197 }, 1198 }, 1199 RestartPolicy: &api.RestartPolicy{ 1200 Interval: helper.TimeToPtr(1 * time.Second), 1201 Attempts: helper.IntToPtr(5), 1202 Delay: helper.TimeToPtr(10 * time.Second), 1203 Mode: helper.StringToPtr("delay"), 1204 }, 1205 ReschedulePolicy: &api.ReschedulePolicy{ 1206 Interval: helper.TimeToPtr(12 * time.Hour), 1207 Attempts: helper.IntToPtr(5), 1208 DelayFunction: helper.StringToPtr("constant"), 1209 Delay: helper.TimeToPtr(30 * time.Second), 1210 Unlimited: helper.BoolToPtr(true), 1211 MaxDelay: helper.TimeToPtr(20 * time.Minute), 1212 }, 1213 Migrate: &api.MigrateStrategy{ 1214 MaxParallel: helper.IntToPtr(12), 1215 HealthCheck: helper.StringToPtr("task_events"), 1216 MinHealthyTime: helper.TimeToPtr(12 * time.Hour), 1217 HealthyDeadline: helper.TimeToPtr(12 * time.Hour), 1218 }, 1219 EphemeralDisk: &api.EphemeralDisk{ 1220 SizeMB: helper.IntToPtr(100), 1221 Sticky: helper.BoolToPtr(true), 1222 Migrate: helper.BoolToPtr(true), 1223 }, 1224 Update: &api.UpdateStrategy{ 1225 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), 1226 MinHealthyTime: helper.TimeToPtr(2 * time.Minute), 1227 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 1228 AutoRevert: helper.BoolToPtr(true), 1229 }, 1230 1231 Meta: map[string]string{ 1232 "key": "value", 1233 }, 1234 Tasks: []*api.Task{ 1235 { 1236 Name: "task1", 1237 Leader: true, 1238 Driver: "docker", 1239 User: "mary", 1240 Config: map[string]interface{}{ 1241 "lol": "code", 1242 }, 1243 Env: map[string]string{ 1244 "hello": "world", 1245 }, 1246 Constraints: []*api.Constraint{ 1247 { 1248 LTarget: "x", 1249 RTarget: "y", 1250 Operand: "z", 1251 }, 1252 }, 1253 1254 Services: []*api.Service{ 1255 { 1256 Id: "id", 1257 Name: "serviceA", 1258 Tags: []string{"1", "2"}, 1259 PortLabel: "foo", 1260 CheckRestart: &api.CheckRestart{ 1261 Limit: 4, 1262 Grace: helper.TimeToPtr(11 * time.Second), 1263 }, 1264 Checks: []api.ServiceCheck{ 1265 { 1266 Id: "hello", 1267 Name: "bar", 1268 Type: "http", 1269 Command: "foo", 1270 Args: []string{"a", "b"}, 1271 Path: "/check", 1272 Protocol: "http", 1273 PortLabel: "foo", 1274 AddressMode: "driver", 1275 Interval: 4 * time.Second, 1276 Timeout: 2 * time.Second, 1277 InitialStatus: "ok", 1278 CheckRestart: &api.CheckRestart{ 1279 Limit: 3, 1280 IgnoreWarnings: true, 1281 }, 1282 }, 1283 { 1284 Id: "check2id", 1285 Name: "check2", 1286 Type: "tcp", 1287 PortLabel: "foo", 1288 Interval: 4 * time.Second, 1289 Timeout: 2 * time.Second, 1290 }, 1291 }, 1292 }, 1293 }, 1294 Resources: &api.Resources{ 1295 CPU: helper.IntToPtr(100), 1296 MemoryMB: helper.IntToPtr(10), 1297 Networks: []*api.NetworkResource{ 1298 { 1299 IP: "10.10.11.1", 1300 MBits: helper.IntToPtr(10), 1301 ReservedPorts: []api.Port{ 1302 { 1303 Label: "http", 1304 Value: 80, 1305 }, 1306 }, 1307 DynamicPorts: []api.Port{ 1308 { 1309 Label: "ssh", 1310 Value: 2000, 1311 }, 1312 }, 1313 }, 1314 }, 1315 }, 1316 Meta: map[string]string{ 1317 "lol": "code", 1318 }, 1319 KillTimeout: helper.TimeToPtr(10 * time.Second), 1320 KillSignal: "SIGQUIT", 1321 LogConfig: &api.LogConfig{ 1322 MaxFiles: helper.IntToPtr(10), 1323 MaxFileSizeMB: helper.IntToPtr(100), 1324 }, 1325 Artifacts: []*api.TaskArtifact{ 1326 { 1327 GetterSource: helper.StringToPtr("source"), 1328 GetterOptions: map[string]string{ 1329 "a": "b", 1330 }, 1331 GetterMode: helper.StringToPtr("dir"), 1332 RelativeDest: helper.StringToPtr("dest"), 1333 }, 1334 }, 1335 Vault: &api.Vault{ 1336 Policies: []string{"a", "b", "c"}, 1337 Env: helper.BoolToPtr(true), 1338 ChangeMode: helper.StringToPtr("c"), 1339 ChangeSignal: helper.StringToPtr("sighup"), 1340 }, 1341 Templates: []*api.Template{ 1342 { 1343 SourcePath: helper.StringToPtr("source"), 1344 DestPath: helper.StringToPtr("dest"), 1345 EmbeddedTmpl: helper.StringToPtr("embedded"), 1346 ChangeMode: helper.StringToPtr("change"), 1347 ChangeSignal: helper.StringToPtr("signal"), 1348 Splay: helper.TimeToPtr(1 * time.Minute), 1349 Perms: helper.StringToPtr("666"), 1350 LeftDelim: helper.StringToPtr("abc"), 1351 RightDelim: helper.StringToPtr("def"), 1352 Envvars: helper.BoolToPtr(true), 1353 VaultGrace: helper.TimeToPtr(3 * time.Second), 1354 }, 1355 }, 1356 DispatchPayload: &api.DispatchPayloadConfig{ 1357 File: "fileA", 1358 }, 1359 }, 1360 }, 1361 }, 1362 }, 1363 VaultToken: helper.StringToPtr("token"), 1364 Status: helper.StringToPtr("status"), 1365 StatusDescription: helper.StringToPtr("status_desc"), 1366 Version: helper.Uint64ToPtr(10), 1367 CreateIndex: helper.Uint64ToPtr(1), 1368 ModifyIndex: helper.Uint64ToPtr(3), 1369 JobModifyIndex: helper.Uint64ToPtr(5), 1370 } 1371 1372 expected := &structs.Job{ 1373 Stop: true, 1374 Region: "global", 1375 Namespace: "foo", 1376 ID: "foo", 1377 ParentID: "lol", 1378 Name: "name", 1379 Type: "service", 1380 Priority: 50, 1381 AllAtOnce: true, 1382 Datacenters: []string{"dc1", "dc2"}, 1383 Constraints: []*structs.Constraint{ 1384 { 1385 LTarget: "a", 1386 RTarget: "b", 1387 Operand: "c", 1388 }, 1389 }, 1390 Update: structs.UpdateStrategy{ 1391 Stagger: 1 * time.Second, 1392 MaxParallel: 5, 1393 }, 1394 Periodic: &structs.PeriodicConfig{ 1395 Enabled: true, 1396 Spec: "spec", 1397 SpecType: "cron", 1398 ProhibitOverlap: true, 1399 TimeZone: "test zone", 1400 }, 1401 ParameterizedJob: &structs.ParameterizedJobConfig{ 1402 Payload: "payload", 1403 MetaRequired: []string{"a", "b"}, 1404 MetaOptional: []string{"c", "d"}, 1405 }, 1406 Payload: []byte("payload"), 1407 Meta: map[string]string{ 1408 "foo": "bar", 1409 }, 1410 TaskGroups: []*structs.TaskGroup{ 1411 { 1412 Name: "group1", 1413 Count: 5, 1414 Constraints: []*structs.Constraint{ 1415 { 1416 LTarget: "x", 1417 RTarget: "y", 1418 Operand: "z", 1419 }, 1420 }, 1421 RestartPolicy: &structs.RestartPolicy{ 1422 Interval: 1 * time.Second, 1423 Attempts: 5, 1424 Delay: 10 * time.Second, 1425 Mode: "delay", 1426 }, 1427 ReschedulePolicy: &structs.ReschedulePolicy{ 1428 Interval: 12 * time.Hour, 1429 Attempts: 5, 1430 DelayFunction: "constant", 1431 Delay: 30 * time.Second, 1432 Unlimited: true, 1433 MaxDelay: 20 * time.Minute, 1434 }, 1435 Migrate: &structs.MigrateStrategy{ 1436 MaxParallel: 12, 1437 HealthCheck: "task_events", 1438 MinHealthyTime: 12 * time.Hour, 1439 HealthyDeadline: 12 * time.Hour, 1440 }, 1441 EphemeralDisk: &structs.EphemeralDisk{ 1442 SizeMB: 100, 1443 Sticky: true, 1444 Migrate: true, 1445 }, 1446 Update: &structs.UpdateStrategy{ 1447 Stagger: 1 * time.Second, 1448 MaxParallel: 5, 1449 HealthCheck: structs.UpdateStrategyHealthCheck_Checks, 1450 MinHealthyTime: 2 * time.Minute, 1451 HealthyDeadline: 5 * time.Minute, 1452 AutoRevert: true, 1453 Canary: 1, 1454 }, 1455 Meta: map[string]string{ 1456 "key": "value", 1457 }, 1458 Tasks: []*structs.Task{ 1459 { 1460 Name: "task1", 1461 Driver: "docker", 1462 Leader: true, 1463 User: "mary", 1464 Config: map[string]interface{}{ 1465 "lol": "code", 1466 }, 1467 Constraints: []*structs.Constraint{ 1468 { 1469 LTarget: "x", 1470 RTarget: "y", 1471 Operand: "z", 1472 }, 1473 }, 1474 Env: map[string]string{ 1475 "hello": "world", 1476 }, 1477 Services: []*structs.Service{ 1478 { 1479 Name: "serviceA", 1480 Tags: []string{"1", "2"}, 1481 PortLabel: "foo", 1482 AddressMode: "auto", 1483 Checks: []*structs.ServiceCheck{ 1484 { 1485 Name: "bar", 1486 Type: "http", 1487 Command: "foo", 1488 Args: []string{"a", "b"}, 1489 Path: "/check", 1490 Protocol: "http", 1491 PortLabel: "foo", 1492 AddressMode: "driver", 1493 Interval: 4 * time.Second, 1494 Timeout: 2 * time.Second, 1495 InitialStatus: "ok", 1496 CheckRestart: &structs.CheckRestart{ 1497 Limit: 3, 1498 Grace: 11 * time.Second, 1499 IgnoreWarnings: true, 1500 }, 1501 }, 1502 { 1503 Name: "check2", 1504 Type: "tcp", 1505 PortLabel: "foo", 1506 Interval: 4 * time.Second, 1507 Timeout: 2 * time.Second, 1508 CheckRestart: &structs.CheckRestart{ 1509 Limit: 4, 1510 Grace: 11 * time.Second, 1511 }, 1512 }, 1513 }, 1514 }, 1515 }, 1516 Resources: &structs.Resources{ 1517 CPU: 100, 1518 MemoryMB: 10, 1519 Networks: []*structs.NetworkResource{ 1520 { 1521 IP: "10.10.11.1", 1522 MBits: 10, 1523 ReservedPorts: []structs.Port{ 1524 { 1525 Label: "http", 1526 Value: 80, 1527 }, 1528 }, 1529 DynamicPorts: []structs.Port{ 1530 { 1531 Label: "ssh", 1532 Value: 2000, 1533 }, 1534 }, 1535 }, 1536 }, 1537 }, 1538 Meta: map[string]string{ 1539 "lol": "code", 1540 }, 1541 KillTimeout: 10 * time.Second, 1542 KillSignal: "SIGQUIT", 1543 LogConfig: &structs.LogConfig{ 1544 MaxFiles: 10, 1545 MaxFileSizeMB: 100, 1546 }, 1547 Artifacts: []*structs.TaskArtifact{ 1548 { 1549 GetterSource: "source", 1550 GetterOptions: map[string]string{ 1551 "a": "b", 1552 }, 1553 GetterMode: "dir", 1554 RelativeDest: "dest", 1555 }, 1556 }, 1557 Vault: &structs.Vault{ 1558 Policies: []string{"a", "b", "c"}, 1559 Env: true, 1560 ChangeMode: "c", 1561 ChangeSignal: "sighup", 1562 }, 1563 Templates: []*structs.Template{ 1564 { 1565 SourcePath: "source", 1566 DestPath: "dest", 1567 EmbeddedTmpl: "embedded", 1568 ChangeMode: "change", 1569 ChangeSignal: "SIGNAL", 1570 Splay: 1 * time.Minute, 1571 Perms: "666", 1572 LeftDelim: "abc", 1573 RightDelim: "def", 1574 Envvars: true, 1575 VaultGrace: 3 * time.Second, 1576 }, 1577 }, 1578 DispatchPayload: &structs.DispatchPayloadConfig{ 1579 File: "fileA", 1580 }, 1581 }, 1582 }, 1583 }, 1584 }, 1585 1586 VaultToken: "token", 1587 } 1588 1589 structsJob := ApiJobToStructJob(apiJob) 1590 1591 if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { 1592 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1593 } 1594 1595 systemAPIJob := &api.Job{ 1596 Stop: helper.BoolToPtr(true), 1597 Region: helper.StringToPtr("global"), 1598 Namespace: helper.StringToPtr("foo"), 1599 ID: helper.StringToPtr("foo"), 1600 ParentID: helper.StringToPtr("lol"), 1601 Name: helper.StringToPtr("name"), 1602 Type: helper.StringToPtr("system"), 1603 Priority: helper.IntToPtr(50), 1604 AllAtOnce: helper.BoolToPtr(true), 1605 Datacenters: []string{"dc1", "dc2"}, 1606 Constraints: []*api.Constraint{ 1607 { 1608 LTarget: "a", 1609 RTarget: "b", 1610 Operand: "c", 1611 }, 1612 }, 1613 TaskGroups: []*api.TaskGroup{ 1614 { 1615 Name: helper.StringToPtr("group1"), 1616 Count: helper.IntToPtr(5), 1617 Constraints: []*api.Constraint{ 1618 { 1619 LTarget: "x", 1620 RTarget: "y", 1621 Operand: "z", 1622 }, 1623 }, 1624 RestartPolicy: &api.RestartPolicy{ 1625 Interval: helper.TimeToPtr(1 * time.Second), 1626 Attempts: helper.IntToPtr(5), 1627 Delay: helper.TimeToPtr(10 * time.Second), 1628 Mode: helper.StringToPtr("delay"), 1629 }, 1630 EphemeralDisk: &api.EphemeralDisk{ 1631 SizeMB: helper.IntToPtr(100), 1632 Sticky: helper.BoolToPtr(true), 1633 Migrate: helper.BoolToPtr(true), 1634 }, 1635 Meta: map[string]string{ 1636 "key": "value", 1637 }, 1638 Tasks: []*api.Task{ 1639 { 1640 Name: "task1", 1641 Leader: true, 1642 Driver: "docker", 1643 User: "mary", 1644 Config: map[string]interface{}{ 1645 "lol": "code", 1646 }, 1647 Env: map[string]string{ 1648 "hello": "world", 1649 }, 1650 Constraints: []*api.Constraint{ 1651 { 1652 LTarget: "x", 1653 RTarget: "y", 1654 Operand: "z", 1655 }, 1656 }, 1657 Resources: &api.Resources{ 1658 CPU: helper.IntToPtr(100), 1659 MemoryMB: helper.IntToPtr(10), 1660 Networks: []*api.NetworkResource{ 1661 { 1662 IP: "10.10.11.1", 1663 MBits: helper.IntToPtr(10), 1664 ReservedPorts: []api.Port{ 1665 { 1666 Label: "http", 1667 Value: 80, 1668 }, 1669 }, 1670 DynamicPorts: []api.Port{ 1671 { 1672 Label: "ssh", 1673 Value: 2000, 1674 }, 1675 }, 1676 }, 1677 }, 1678 }, 1679 Meta: map[string]string{ 1680 "lol": "code", 1681 }, 1682 KillTimeout: helper.TimeToPtr(10 * time.Second), 1683 KillSignal: "SIGQUIT", 1684 LogConfig: &api.LogConfig{ 1685 MaxFiles: helper.IntToPtr(10), 1686 MaxFileSizeMB: helper.IntToPtr(100), 1687 }, 1688 Artifacts: []*api.TaskArtifact{ 1689 { 1690 GetterSource: helper.StringToPtr("source"), 1691 GetterOptions: map[string]string{ 1692 "a": "b", 1693 }, 1694 GetterMode: helper.StringToPtr("dir"), 1695 RelativeDest: helper.StringToPtr("dest"), 1696 }, 1697 }, 1698 DispatchPayload: &api.DispatchPayloadConfig{ 1699 File: "fileA", 1700 }, 1701 }, 1702 }, 1703 }, 1704 }, 1705 Status: helper.StringToPtr("status"), 1706 StatusDescription: helper.StringToPtr("status_desc"), 1707 Version: helper.Uint64ToPtr(10), 1708 CreateIndex: helper.Uint64ToPtr(1), 1709 ModifyIndex: helper.Uint64ToPtr(3), 1710 JobModifyIndex: helper.Uint64ToPtr(5), 1711 } 1712 1713 expectedSystemJob := &structs.Job{ 1714 Stop: true, 1715 Region: "global", 1716 Namespace: "foo", 1717 ID: "foo", 1718 ParentID: "lol", 1719 Name: "name", 1720 Type: "system", 1721 Priority: 50, 1722 AllAtOnce: true, 1723 Datacenters: []string{"dc1", "dc2"}, 1724 Constraints: []*structs.Constraint{ 1725 { 1726 LTarget: "a", 1727 RTarget: "b", 1728 Operand: "c", 1729 }, 1730 }, 1731 TaskGroups: []*structs.TaskGroup{ 1732 { 1733 Name: "group1", 1734 Count: 5, 1735 Constraints: []*structs.Constraint{ 1736 { 1737 LTarget: "x", 1738 RTarget: "y", 1739 Operand: "z", 1740 }, 1741 }, 1742 RestartPolicy: &structs.RestartPolicy{ 1743 Interval: 1 * time.Second, 1744 Attempts: 5, 1745 Delay: 10 * time.Second, 1746 Mode: "delay", 1747 }, 1748 EphemeralDisk: &structs.EphemeralDisk{ 1749 SizeMB: 100, 1750 Sticky: true, 1751 Migrate: true, 1752 }, 1753 Meta: map[string]string{ 1754 "key": "value", 1755 }, 1756 Tasks: []*structs.Task{ 1757 { 1758 Name: "task1", 1759 Driver: "docker", 1760 Leader: true, 1761 User: "mary", 1762 Config: map[string]interface{}{ 1763 "lol": "code", 1764 }, 1765 Constraints: []*structs.Constraint{ 1766 { 1767 LTarget: "x", 1768 RTarget: "y", 1769 Operand: "z", 1770 }, 1771 }, 1772 Env: map[string]string{ 1773 "hello": "world", 1774 }, 1775 Resources: &structs.Resources{ 1776 CPU: 100, 1777 MemoryMB: 10, 1778 Networks: []*structs.NetworkResource{ 1779 { 1780 IP: "10.10.11.1", 1781 MBits: 10, 1782 ReservedPorts: []structs.Port{ 1783 { 1784 Label: "http", 1785 Value: 80, 1786 }, 1787 }, 1788 DynamicPorts: []structs.Port{ 1789 { 1790 Label: "ssh", 1791 Value: 2000, 1792 }, 1793 }, 1794 }, 1795 }, 1796 }, 1797 Meta: map[string]string{ 1798 "lol": "code", 1799 }, 1800 KillTimeout: 10 * time.Second, 1801 KillSignal: "SIGQUIT", 1802 LogConfig: &structs.LogConfig{ 1803 MaxFiles: 10, 1804 MaxFileSizeMB: 100, 1805 }, 1806 Artifacts: []*structs.TaskArtifact{ 1807 { 1808 GetterSource: "source", 1809 GetterOptions: map[string]string{ 1810 "a": "b", 1811 }, 1812 GetterMode: "dir", 1813 RelativeDest: "dest", 1814 }, 1815 }, 1816 DispatchPayload: &structs.DispatchPayloadConfig{ 1817 File: "fileA", 1818 }, 1819 }, 1820 }, 1821 }, 1822 }, 1823 } 1824 1825 systemStructsJob := ApiJobToStructJob(systemAPIJob) 1826 1827 if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 { 1828 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1829 } 1830 }