github.com/remilapeyre/nomad@v0.8.5/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 Update: &api.UpdateStrategy{ 1215 Stagger: helper.TimeToPtr(1 * time.Second), 1216 MaxParallel: helper.IntToPtr(5), 1217 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 1218 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 1219 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 1220 ProgressDeadline: helper.TimeToPtr(3 * time.Minute), 1221 AutoRevert: helper.BoolToPtr(false), 1222 Canary: helper.IntToPtr(1), 1223 }, 1224 Periodic: &api.PeriodicConfig{ 1225 Enabled: helper.BoolToPtr(true), 1226 Spec: helper.StringToPtr("spec"), 1227 SpecType: helper.StringToPtr("cron"), 1228 ProhibitOverlap: helper.BoolToPtr(true), 1229 TimeZone: helper.StringToPtr("test zone"), 1230 }, 1231 ParameterizedJob: &api.ParameterizedJobConfig{ 1232 Payload: "payload", 1233 MetaRequired: []string{"a", "b"}, 1234 MetaOptional: []string{"c", "d"}, 1235 }, 1236 Payload: []byte("payload"), 1237 Meta: map[string]string{ 1238 "foo": "bar", 1239 }, 1240 TaskGroups: []*api.TaskGroup{ 1241 { 1242 Name: helper.StringToPtr("group1"), 1243 Count: helper.IntToPtr(5), 1244 Constraints: []*api.Constraint{ 1245 { 1246 LTarget: "x", 1247 RTarget: "y", 1248 Operand: "z", 1249 }, 1250 }, 1251 RestartPolicy: &api.RestartPolicy{ 1252 Interval: helper.TimeToPtr(1 * time.Second), 1253 Attempts: helper.IntToPtr(5), 1254 Delay: helper.TimeToPtr(10 * time.Second), 1255 Mode: helper.StringToPtr("delay"), 1256 }, 1257 ReschedulePolicy: &api.ReschedulePolicy{ 1258 Interval: helper.TimeToPtr(12 * time.Hour), 1259 Attempts: helper.IntToPtr(5), 1260 DelayFunction: helper.StringToPtr("constant"), 1261 Delay: helper.TimeToPtr(30 * time.Second), 1262 Unlimited: helper.BoolToPtr(true), 1263 MaxDelay: helper.TimeToPtr(20 * time.Minute), 1264 }, 1265 Migrate: &api.MigrateStrategy{ 1266 MaxParallel: helper.IntToPtr(12), 1267 HealthCheck: helper.StringToPtr("task_events"), 1268 MinHealthyTime: helper.TimeToPtr(12 * time.Hour), 1269 HealthyDeadline: helper.TimeToPtr(12 * time.Hour), 1270 }, 1271 EphemeralDisk: &api.EphemeralDisk{ 1272 SizeMB: helper.IntToPtr(100), 1273 Sticky: helper.BoolToPtr(true), 1274 Migrate: helper.BoolToPtr(true), 1275 }, 1276 Update: &api.UpdateStrategy{ 1277 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), 1278 MinHealthyTime: helper.TimeToPtr(2 * time.Minute), 1279 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 1280 ProgressDeadline: helper.TimeToPtr(5 * time.Minute), 1281 AutoRevert: helper.BoolToPtr(true), 1282 }, 1283 1284 Meta: map[string]string{ 1285 "key": "value", 1286 }, 1287 Tasks: []*api.Task{ 1288 { 1289 Name: "task1", 1290 Leader: true, 1291 Driver: "docker", 1292 User: "mary", 1293 Config: map[string]interface{}{ 1294 "lol": "code", 1295 }, 1296 Env: map[string]string{ 1297 "hello": "world", 1298 }, 1299 Constraints: []*api.Constraint{ 1300 { 1301 LTarget: "x", 1302 RTarget: "y", 1303 Operand: "z", 1304 }, 1305 }, 1306 1307 Services: []*api.Service{ 1308 { 1309 Id: "id", 1310 Name: "serviceA", 1311 Tags: []string{"1", "2"}, 1312 CanaryTags: []string{"3", "4"}, 1313 PortLabel: "foo", 1314 CheckRestart: &api.CheckRestart{ 1315 Limit: 4, 1316 Grace: helper.TimeToPtr(11 * time.Second), 1317 }, 1318 Checks: []api.ServiceCheck{ 1319 { 1320 Id: "hello", 1321 Name: "bar", 1322 Type: "http", 1323 Command: "foo", 1324 Args: []string{"a", "b"}, 1325 Path: "/check", 1326 Protocol: "http", 1327 PortLabel: "foo", 1328 AddressMode: "driver", 1329 GRPCService: "foo.Bar", 1330 GRPCUseTLS: true, 1331 Interval: 4 * time.Second, 1332 Timeout: 2 * time.Second, 1333 InitialStatus: "ok", 1334 CheckRestart: &api.CheckRestart{ 1335 Limit: 3, 1336 IgnoreWarnings: true, 1337 }, 1338 }, 1339 { 1340 Id: "check2id", 1341 Name: "check2", 1342 Type: "tcp", 1343 PortLabel: "foo", 1344 Interval: 4 * time.Second, 1345 Timeout: 2 * time.Second, 1346 }, 1347 }, 1348 }, 1349 }, 1350 Resources: &api.Resources{ 1351 CPU: helper.IntToPtr(100), 1352 MemoryMB: helper.IntToPtr(10), 1353 Networks: []*api.NetworkResource{ 1354 { 1355 IP: "10.10.11.1", 1356 MBits: helper.IntToPtr(10), 1357 ReservedPorts: []api.Port{ 1358 { 1359 Label: "http", 1360 Value: 80, 1361 }, 1362 }, 1363 DynamicPorts: []api.Port{ 1364 { 1365 Label: "ssh", 1366 Value: 2000, 1367 }, 1368 }, 1369 }, 1370 }, 1371 }, 1372 Meta: map[string]string{ 1373 "lol": "code", 1374 }, 1375 KillTimeout: helper.TimeToPtr(10 * time.Second), 1376 KillSignal: "SIGQUIT", 1377 LogConfig: &api.LogConfig{ 1378 MaxFiles: helper.IntToPtr(10), 1379 MaxFileSizeMB: helper.IntToPtr(100), 1380 }, 1381 Artifacts: []*api.TaskArtifact{ 1382 { 1383 GetterSource: helper.StringToPtr("source"), 1384 GetterOptions: map[string]string{ 1385 "a": "b", 1386 }, 1387 GetterMode: helper.StringToPtr("dir"), 1388 RelativeDest: helper.StringToPtr("dest"), 1389 }, 1390 }, 1391 Vault: &api.Vault{ 1392 Policies: []string{"a", "b", "c"}, 1393 Env: helper.BoolToPtr(true), 1394 ChangeMode: helper.StringToPtr("c"), 1395 ChangeSignal: helper.StringToPtr("sighup"), 1396 }, 1397 Templates: []*api.Template{ 1398 { 1399 SourcePath: helper.StringToPtr("source"), 1400 DestPath: helper.StringToPtr("dest"), 1401 EmbeddedTmpl: helper.StringToPtr("embedded"), 1402 ChangeMode: helper.StringToPtr("change"), 1403 ChangeSignal: helper.StringToPtr("signal"), 1404 Splay: helper.TimeToPtr(1 * time.Minute), 1405 Perms: helper.StringToPtr("666"), 1406 LeftDelim: helper.StringToPtr("abc"), 1407 RightDelim: helper.StringToPtr("def"), 1408 Envvars: helper.BoolToPtr(true), 1409 VaultGrace: helper.TimeToPtr(3 * time.Second), 1410 }, 1411 }, 1412 DispatchPayload: &api.DispatchPayloadConfig{ 1413 File: "fileA", 1414 }, 1415 }, 1416 }, 1417 }, 1418 }, 1419 VaultToken: helper.StringToPtr("token"), 1420 Status: helper.StringToPtr("status"), 1421 StatusDescription: helper.StringToPtr("status_desc"), 1422 Version: helper.Uint64ToPtr(10), 1423 CreateIndex: helper.Uint64ToPtr(1), 1424 ModifyIndex: helper.Uint64ToPtr(3), 1425 JobModifyIndex: helper.Uint64ToPtr(5), 1426 } 1427 1428 expected := &structs.Job{ 1429 Stop: true, 1430 Region: "global", 1431 Namespace: "foo", 1432 ID: "foo", 1433 ParentID: "lol", 1434 Name: "name", 1435 Type: "service", 1436 Priority: 50, 1437 AllAtOnce: true, 1438 Datacenters: []string{"dc1", "dc2"}, 1439 Constraints: []*structs.Constraint{ 1440 { 1441 LTarget: "a", 1442 RTarget: "b", 1443 Operand: "c", 1444 }, 1445 }, 1446 Update: structs.UpdateStrategy{ 1447 Stagger: 1 * time.Second, 1448 MaxParallel: 5, 1449 }, 1450 Periodic: &structs.PeriodicConfig{ 1451 Enabled: true, 1452 Spec: "spec", 1453 SpecType: "cron", 1454 ProhibitOverlap: true, 1455 TimeZone: "test zone", 1456 }, 1457 ParameterizedJob: &structs.ParameterizedJobConfig{ 1458 Payload: "payload", 1459 MetaRequired: []string{"a", "b"}, 1460 MetaOptional: []string{"c", "d"}, 1461 }, 1462 Payload: []byte("payload"), 1463 Meta: map[string]string{ 1464 "foo": "bar", 1465 }, 1466 TaskGroups: []*structs.TaskGroup{ 1467 { 1468 Name: "group1", 1469 Count: 5, 1470 Constraints: []*structs.Constraint{ 1471 { 1472 LTarget: "x", 1473 RTarget: "y", 1474 Operand: "z", 1475 }, 1476 }, 1477 RestartPolicy: &structs.RestartPolicy{ 1478 Interval: 1 * time.Second, 1479 Attempts: 5, 1480 Delay: 10 * time.Second, 1481 Mode: "delay", 1482 }, 1483 ReschedulePolicy: &structs.ReschedulePolicy{ 1484 Interval: 12 * time.Hour, 1485 Attempts: 5, 1486 DelayFunction: "constant", 1487 Delay: 30 * time.Second, 1488 Unlimited: true, 1489 MaxDelay: 20 * time.Minute, 1490 }, 1491 Migrate: &structs.MigrateStrategy{ 1492 MaxParallel: 12, 1493 HealthCheck: "task_events", 1494 MinHealthyTime: 12 * time.Hour, 1495 HealthyDeadline: 12 * time.Hour, 1496 }, 1497 EphemeralDisk: &structs.EphemeralDisk{ 1498 SizeMB: 100, 1499 Sticky: true, 1500 Migrate: true, 1501 }, 1502 Update: &structs.UpdateStrategy{ 1503 Stagger: 1 * time.Second, 1504 MaxParallel: 5, 1505 HealthCheck: structs.UpdateStrategyHealthCheck_Checks, 1506 MinHealthyTime: 2 * time.Minute, 1507 HealthyDeadline: 5 * time.Minute, 1508 ProgressDeadline: 5 * time.Minute, 1509 AutoRevert: true, 1510 Canary: 1, 1511 }, 1512 Meta: map[string]string{ 1513 "key": "value", 1514 }, 1515 Tasks: []*structs.Task{ 1516 { 1517 Name: "task1", 1518 Driver: "docker", 1519 Leader: true, 1520 User: "mary", 1521 Config: map[string]interface{}{ 1522 "lol": "code", 1523 }, 1524 Constraints: []*structs.Constraint{ 1525 { 1526 LTarget: "x", 1527 RTarget: "y", 1528 Operand: "z", 1529 }, 1530 }, 1531 Env: map[string]string{ 1532 "hello": "world", 1533 }, 1534 Services: []*structs.Service{ 1535 { 1536 Name: "serviceA", 1537 Tags: []string{"1", "2"}, 1538 CanaryTags: []string{"3", "4"}, 1539 PortLabel: "foo", 1540 AddressMode: "auto", 1541 Checks: []*structs.ServiceCheck{ 1542 { 1543 Name: "bar", 1544 Type: "http", 1545 Command: "foo", 1546 Args: []string{"a", "b"}, 1547 Path: "/check", 1548 Protocol: "http", 1549 PortLabel: "foo", 1550 AddressMode: "driver", 1551 Interval: 4 * time.Second, 1552 Timeout: 2 * time.Second, 1553 InitialStatus: "ok", 1554 GRPCService: "foo.Bar", 1555 GRPCUseTLS: true, 1556 CheckRestart: &structs.CheckRestart{ 1557 Limit: 3, 1558 Grace: 11 * time.Second, 1559 IgnoreWarnings: true, 1560 }, 1561 }, 1562 { 1563 Name: "check2", 1564 Type: "tcp", 1565 PortLabel: "foo", 1566 Interval: 4 * time.Second, 1567 Timeout: 2 * time.Second, 1568 CheckRestart: &structs.CheckRestart{ 1569 Limit: 4, 1570 Grace: 11 * time.Second, 1571 }, 1572 }, 1573 }, 1574 }, 1575 }, 1576 Resources: &structs.Resources{ 1577 CPU: 100, 1578 MemoryMB: 10, 1579 Networks: []*structs.NetworkResource{ 1580 { 1581 IP: "10.10.11.1", 1582 MBits: 10, 1583 ReservedPorts: []structs.Port{ 1584 { 1585 Label: "http", 1586 Value: 80, 1587 }, 1588 }, 1589 DynamicPorts: []structs.Port{ 1590 { 1591 Label: "ssh", 1592 Value: 2000, 1593 }, 1594 }, 1595 }, 1596 }, 1597 }, 1598 Meta: map[string]string{ 1599 "lol": "code", 1600 }, 1601 KillTimeout: 10 * time.Second, 1602 KillSignal: "SIGQUIT", 1603 LogConfig: &structs.LogConfig{ 1604 MaxFiles: 10, 1605 MaxFileSizeMB: 100, 1606 }, 1607 Artifacts: []*structs.TaskArtifact{ 1608 { 1609 GetterSource: "source", 1610 GetterOptions: map[string]string{ 1611 "a": "b", 1612 }, 1613 GetterMode: "dir", 1614 RelativeDest: "dest", 1615 }, 1616 }, 1617 Vault: &structs.Vault{ 1618 Policies: []string{"a", "b", "c"}, 1619 Env: true, 1620 ChangeMode: "c", 1621 ChangeSignal: "sighup", 1622 }, 1623 Templates: []*structs.Template{ 1624 { 1625 SourcePath: "source", 1626 DestPath: "dest", 1627 EmbeddedTmpl: "embedded", 1628 ChangeMode: "change", 1629 ChangeSignal: "SIGNAL", 1630 Splay: 1 * time.Minute, 1631 Perms: "666", 1632 LeftDelim: "abc", 1633 RightDelim: "def", 1634 Envvars: true, 1635 VaultGrace: 3 * time.Second, 1636 }, 1637 }, 1638 DispatchPayload: &structs.DispatchPayloadConfig{ 1639 File: "fileA", 1640 }, 1641 }, 1642 }, 1643 }, 1644 }, 1645 1646 VaultToken: "token", 1647 } 1648 1649 structsJob := ApiJobToStructJob(apiJob) 1650 1651 if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { 1652 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1653 } 1654 1655 systemAPIJob := &api.Job{ 1656 Stop: helper.BoolToPtr(true), 1657 Region: helper.StringToPtr("global"), 1658 Namespace: helper.StringToPtr("foo"), 1659 ID: helper.StringToPtr("foo"), 1660 ParentID: helper.StringToPtr("lol"), 1661 Name: helper.StringToPtr("name"), 1662 Type: helper.StringToPtr("system"), 1663 Priority: helper.IntToPtr(50), 1664 AllAtOnce: helper.BoolToPtr(true), 1665 Datacenters: []string{"dc1", "dc2"}, 1666 Constraints: []*api.Constraint{ 1667 { 1668 LTarget: "a", 1669 RTarget: "b", 1670 Operand: "c", 1671 }, 1672 }, 1673 TaskGroups: []*api.TaskGroup{ 1674 { 1675 Name: helper.StringToPtr("group1"), 1676 Count: helper.IntToPtr(5), 1677 Constraints: []*api.Constraint{ 1678 { 1679 LTarget: "x", 1680 RTarget: "y", 1681 Operand: "z", 1682 }, 1683 }, 1684 RestartPolicy: &api.RestartPolicy{ 1685 Interval: helper.TimeToPtr(1 * time.Second), 1686 Attempts: helper.IntToPtr(5), 1687 Delay: helper.TimeToPtr(10 * time.Second), 1688 Mode: helper.StringToPtr("delay"), 1689 }, 1690 EphemeralDisk: &api.EphemeralDisk{ 1691 SizeMB: helper.IntToPtr(100), 1692 Sticky: helper.BoolToPtr(true), 1693 Migrate: helper.BoolToPtr(true), 1694 }, 1695 Meta: map[string]string{ 1696 "key": "value", 1697 }, 1698 Tasks: []*api.Task{ 1699 { 1700 Name: "task1", 1701 Leader: true, 1702 Driver: "docker", 1703 User: "mary", 1704 Config: map[string]interface{}{ 1705 "lol": "code", 1706 }, 1707 Env: map[string]string{ 1708 "hello": "world", 1709 }, 1710 Constraints: []*api.Constraint{ 1711 { 1712 LTarget: "x", 1713 RTarget: "y", 1714 Operand: "z", 1715 }, 1716 }, 1717 Resources: &api.Resources{ 1718 CPU: helper.IntToPtr(100), 1719 MemoryMB: helper.IntToPtr(10), 1720 Networks: []*api.NetworkResource{ 1721 { 1722 IP: "10.10.11.1", 1723 MBits: helper.IntToPtr(10), 1724 ReservedPorts: []api.Port{ 1725 { 1726 Label: "http", 1727 Value: 80, 1728 }, 1729 }, 1730 DynamicPorts: []api.Port{ 1731 { 1732 Label: "ssh", 1733 Value: 2000, 1734 }, 1735 }, 1736 }, 1737 }, 1738 }, 1739 Meta: map[string]string{ 1740 "lol": "code", 1741 }, 1742 KillTimeout: helper.TimeToPtr(10 * time.Second), 1743 KillSignal: "SIGQUIT", 1744 LogConfig: &api.LogConfig{ 1745 MaxFiles: helper.IntToPtr(10), 1746 MaxFileSizeMB: helper.IntToPtr(100), 1747 }, 1748 Artifacts: []*api.TaskArtifact{ 1749 { 1750 GetterSource: helper.StringToPtr("source"), 1751 GetterOptions: map[string]string{ 1752 "a": "b", 1753 }, 1754 GetterMode: helper.StringToPtr("dir"), 1755 RelativeDest: helper.StringToPtr("dest"), 1756 }, 1757 }, 1758 DispatchPayload: &api.DispatchPayloadConfig{ 1759 File: "fileA", 1760 }, 1761 }, 1762 }, 1763 }, 1764 }, 1765 Status: helper.StringToPtr("status"), 1766 StatusDescription: helper.StringToPtr("status_desc"), 1767 Version: helper.Uint64ToPtr(10), 1768 CreateIndex: helper.Uint64ToPtr(1), 1769 ModifyIndex: helper.Uint64ToPtr(3), 1770 JobModifyIndex: helper.Uint64ToPtr(5), 1771 } 1772 1773 expectedSystemJob := &structs.Job{ 1774 Stop: true, 1775 Region: "global", 1776 Namespace: "foo", 1777 ID: "foo", 1778 ParentID: "lol", 1779 Name: "name", 1780 Type: "system", 1781 Priority: 50, 1782 AllAtOnce: true, 1783 Datacenters: []string{"dc1", "dc2"}, 1784 Constraints: []*structs.Constraint{ 1785 { 1786 LTarget: "a", 1787 RTarget: "b", 1788 Operand: "c", 1789 }, 1790 }, 1791 TaskGroups: []*structs.TaskGroup{ 1792 { 1793 Name: "group1", 1794 Count: 5, 1795 Constraints: []*structs.Constraint{ 1796 { 1797 LTarget: "x", 1798 RTarget: "y", 1799 Operand: "z", 1800 }, 1801 }, 1802 RestartPolicy: &structs.RestartPolicy{ 1803 Interval: 1 * time.Second, 1804 Attempts: 5, 1805 Delay: 10 * time.Second, 1806 Mode: "delay", 1807 }, 1808 EphemeralDisk: &structs.EphemeralDisk{ 1809 SizeMB: 100, 1810 Sticky: true, 1811 Migrate: true, 1812 }, 1813 Meta: map[string]string{ 1814 "key": "value", 1815 }, 1816 Tasks: []*structs.Task{ 1817 { 1818 Name: "task1", 1819 Driver: "docker", 1820 Leader: true, 1821 User: "mary", 1822 Config: map[string]interface{}{ 1823 "lol": "code", 1824 }, 1825 Constraints: []*structs.Constraint{ 1826 { 1827 LTarget: "x", 1828 RTarget: "y", 1829 Operand: "z", 1830 }, 1831 }, 1832 Env: map[string]string{ 1833 "hello": "world", 1834 }, 1835 Resources: &structs.Resources{ 1836 CPU: 100, 1837 MemoryMB: 10, 1838 Networks: []*structs.NetworkResource{ 1839 { 1840 IP: "10.10.11.1", 1841 MBits: 10, 1842 ReservedPorts: []structs.Port{ 1843 { 1844 Label: "http", 1845 Value: 80, 1846 }, 1847 }, 1848 DynamicPorts: []structs.Port{ 1849 { 1850 Label: "ssh", 1851 Value: 2000, 1852 }, 1853 }, 1854 }, 1855 }, 1856 }, 1857 Meta: map[string]string{ 1858 "lol": "code", 1859 }, 1860 KillTimeout: 10 * time.Second, 1861 KillSignal: "SIGQUIT", 1862 LogConfig: &structs.LogConfig{ 1863 MaxFiles: 10, 1864 MaxFileSizeMB: 100, 1865 }, 1866 Artifacts: []*structs.TaskArtifact{ 1867 { 1868 GetterSource: "source", 1869 GetterOptions: map[string]string{ 1870 "a": "b", 1871 }, 1872 GetterMode: "dir", 1873 RelativeDest: "dest", 1874 }, 1875 }, 1876 DispatchPayload: &structs.DispatchPayloadConfig{ 1877 File: "fileA", 1878 }, 1879 }, 1880 }, 1881 }, 1882 }, 1883 } 1884 1885 systemStructsJob := ApiJobToStructJob(systemAPIJob) 1886 1887 if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 { 1888 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1889 } 1890 }