github.com/smithx10/nomad@v0.9.1-rc1/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 := 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 := 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 := 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 := 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 := MockJob() 989 args := api.JobPlanRequest{ 990 Job: job, 991 Diff: true, 992 WriteRequest: api.WriteRequest{ 993 Region: "global", 994 Namespace: api.DefaultNamespace, 995 }, 996 } 997 buf := encodeReq(args) 998 999 // Make the HTTP request 1000 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) 1001 if err != nil { 1002 t.Fatalf("err: %v", err) 1003 } 1004 respW := httptest.NewRecorder() 1005 1006 // Make the request 1007 obj, err := s.Server.JobSpecificRequest(respW, req) 1008 if err != nil { 1009 t.Fatalf("err: %v", err) 1010 } 1011 1012 // Check the response 1013 plan := obj.(structs.JobPlanResponse) 1014 if plan.Annotations == nil { 1015 t.Fatalf("bad: %v", plan) 1016 } 1017 1018 if plan.Diff == nil { 1019 t.Fatalf("bad: %v", plan) 1020 } 1021 }) 1022 } 1023 1024 func TestHTTP_JobDispatch(t *testing.T) { 1025 t.Parallel() 1026 httpTest(t, nil, func(s *TestAgent) { 1027 // Create the parameterized job 1028 job := mock.BatchJob() 1029 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 1030 1031 args := structs.JobRegisterRequest{ 1032 Job: job, 1033 WriteRequest: structs.WriteRequest{ 1034 Region: "global", 1035 Namespace: structs.DefaultNamespace, 1036 }, 1037 } 1038 var resp structs.JobRegisterResponse 1039 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 1040 t.Fatalf("err: %v", err) 1041 } 1042 1043 // Make the request 1044 respW := httptest.NewRecorder() 1045 args2 := structs.JobDispatchRequest{ 1046 WriteRequest: structs.WriteRequest{ 1047 Region: "global", 1048 Namespace: structs.DefaultNamespace, 1049 }, 1050 } 1051 buf := encodeReq(args2) 1052 1053 // Make the HTTP request 1054 req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) 1055 if err != nil { 1056 t.Fatalf("err: %v", err) 1057 } 1058 respW.Flush() 1059 1060 // Make the request 1061 obj, err := s.Server.JobSpecificRequest(respW, req2) 1062 if err != nil { 1063 t.Fatalf("err: %v", err) 1064 } 1065 1066 // Check the response 1067 dispatch := obj.(structs.JobDispatchResponse) 1068 if dispatch.EvalID == "" { 1069 t.Fatalf("bad: %v", dispatch) 1070 } 1071 1072 if dispatch.DispatchedJobID == "" { 1073 t.Fatalf("bad: %v", dispatch) 1074 } 1075 }) 1076 } 1077 1078 func TestHTTP_JobRevert(t *testing.T) { 1079 t.Parallel() 1080 httpTest(t, nil, func(s *TestAgent) { 1081 // Create the job and register it twice 1082 job := mock.Job() 1083 regReq := structs.JobRegisterRequest{ 1084 Job: job, 1085 WriteRequest: structs.WriteRequest{ 1086 Region: "global", 1087 Namespace: structs.DefaultNamespace, 1088 }, 1089 } 1090 var regResp structs.JobRegisterResponse 1091 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1092 t.Fatalf("err: %v", err) 1093 } 1094 1095 // Change the job to get a new version 1096 job.Datacenters = append(job.Datacenters, "foo") 1097 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1098 t.Fatalf("err: %v", err) 1099 } 1100 1101 args := structs.JobRevertRequest{ 1102 JobID: job.ID, 1103 JobVersion: 0, 1104 WriteRequest: structs.WriteRequest{ 1105 Region: "global", 1106 Namespace: structs.DefaultNamespace, 1107 }, 1108 } 1109 buf := encodeReq(args) 1110 1111 // Make the HTTP request 1112 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) 1113 if err != nil { 1114 t.Fatalf("err: %v", err) 1115 } 1116 respW := httptest.NewRecorder() 1117 1118 // Make the request 1119 obj, err := s.Server.JobSpecificRequest(respW, req) 1120 if err != nil { 1121 t.Fatalf("err: %v", err) 1122 } 1123 1124 // Check the response 1125 revertResp := obj.(structs.JobRegisterResponse) 1126 if revertResp.EvalID == "" { 1127 t.Fatalf("bad: %v", revertResp) 1128 } 1129 1130 // Check for the index 1131 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1132 t.Fatalf("missing index") 1133 } 1134 }) 1135 } 1136 1137 func TestHTTP_JobStable(t *testing.T) { 1138 t.Parallel() 1139 httpTest(t, nil, func(s *TestAgent) { 1140 // Create the job and register it twice 1141 job := mock.Job() 1142 regReq := structs.JobRegisterRequest{ 1143 Job: job, 1144 WriteRequest: structs.WriteRequest{ 1145 Region: "global", 1146 Namespace: structs.DefaultNamespace, 1147 }, 1148 } 1149 var regResp structs.JobRegisterResponse 1150 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1151 t.Fatalf("err: %v", err) 1152 } 1153 1154 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1155 t.Fatalf("err: %v", err) 1156 } 1157 1158 args := structs.JobStabilityRequest{ 1159 JobID: job.ID, 1160 JobVersion: 0, 1161 Stable: true, 1162 WriteRequest: structs.WriteRequest{ 1163 Region: "global", 1164 Namespace: structs.DefaultNamespace, 1165 }, 1166 } 1167 buf := encodeReq(args) 1168 1169 // Make the HTTP request 1170 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf) 1171 if err != nil { 1172 t.Fatalf("err: %v", err) 1173 } 1174 respW := httptest.NewRecorder() 1175 1176 // Make the request 1177 obj, err := s.Server.JobSpecificRequest(respW, req) 1178 if err != nil { 1179 t.Fatalf("err: %v", err) 1180 } 1181 1182 // Check the response 1183 stableResp := obj.(structs.JobStabilityResponse) 1184 if stableResp.Index == 0 { 1185 t.Fatalf("bad: %v", stableResp) 1186 } 1187 1188 // Check for the index 1189 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1190 t.Fatalf("missing index") 1191 } 1192 }) 1193 } 1194 1195 func TestJobs_ApiJobToStructsJob(t *testing.T) { 1196 apiJob := &api.Job{ 1197 Stop: helper.BoolToPtr(true), 1198 Region: helper.StringToPtr("global"), 1199 Namespace: helper.StringToPtr("foo"), 1200 ID: helper.StringToPtr("foo"), 1201 ParentID: helper.StringToPtr("lol"), 1202 Name: helper.StringToPtr("name"), 1203 Type: helper.StringToPtr("service"), 1204 Priority: helper.IntToPtr(50), 1205 AllAtOnce: helper.BoolToPtr(true), 1206 Datacenters: []string{"dc1", "dc2"}, 1207 Constraints: []*api.Constraint{ 1208 { 1209 LTarget: "a", 1210 RTarget: "b", 1211 Operand: "c", 1212 }, 1213 }, 1214 Affinities: []*api.Affinity{ 1215 { 1216 LTarget: "a", 1217 RTarget: "b", 1218 Operand: "c", 1219 Weight: helper.Int8ToPtr(50), 1220 }, 1221 }, 1222 Update: &api.UpdateStrategy{ 1223 Stagger: helper.TimeToPtr(1 * time.Second), 1224 MaxParallel: helper.IntToPtr(5), 1225 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 1226 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 1227 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 1228 ProgressDeadline: helper.TimeToPtr(3 * time.Minute), 1229 AutoRevert: helper.BoolToPtr(false), 1230 Canary: helper.IntToPtr(1), 1231 }, 1232 Spreads: []*api.Spread{ 1233 { 1234 Attribute: "${meta.rack}", 1235 Weight: helper.Int8ToPtr(100), 1236 SpreadTarget: []*api.SpreadTarget{ 1237 { 1238 Value: "r1", 1239 Percent: 50, 1240 }, 1241 }, 1242 }, 1243 }, 1244 Periodic: &api.PeriodicConfig{ 1245 Enabled: helper.BoolToPtr(true), 1246 Spec: helper.StringToPtr("spec"), 1247 SpecType: helper.StringToPtr("cron"), 1248 ProhibitOverlap: helper.BoolToPtr(true), 1249 TimeZone: helper.StringToPtr("test zone"), 1250 }, 1251 ParameterizedJob: &api.ParameterizedJobConfig{ 1252 Payload: "payload", 1253 MetaRequired: []string{"a", "b"}, 1254 MetaOptional: []string{"c", "d"}, 1255 }, 1256 Payload: []byte("payload"), 1257 Meta: map[string]string{ 1258 "foo": "bar", 1259 }, 1260 TaskGroups: []*api.TaskGroup{ 1261 { 1262 Name: helper.StringToPtr("group1"), 1263 Count: helper.IntToPtr(5), 1264 Constraints: []*api.Constraint{ 1265 { 1266 LTarget: "x", 1267 RTarget: "y", 1268 Operand: "z", 1269 }, 1270 }, 1271 Affinities: []*api.Affinity{ 1272 { 1273 LTarget: "x", 1274 RTarget: "y", 1275 Operand: "z", 1276 Weight: helper.Int8ToPtr(100), 1277 }, 1278 }, 1279 RestartPolicy: &api.RestartPolicy{ 1280 Interval: helper.TimeToPtr(1 * time.Second), 1281 Attempts: helper.IntToPtr(5), 1282 Delay: helper.TimeToPtr(10 * time.Second), 1283 Mode: helper.StringToPtr("delay"), 1284 }, 1285 ReschedulePolicy: &api.ReschedulePolicy{ 1286 Interval: helper.TimeToPtr(12 * time.Hour), 1287 Attempts: helper.IntToPtr(5), 1288 DelayFunction: helper.StringToPtr("constant"), 1289 Delay: helper.TimeToPtr(30 * time.Second), 1290 Unlimited: helper.BoolToPtr(true), 1291 MaxDelay: helper.TimeToPtr(20 * time.Minute), 1292 }, 1293 Migrate: &api.MigrateStrategy{ 1294 MaxParallel: helper.IntToPtr(12), 1295 HealthCheck: helper.StringToPtr("task_events"), 1296 MinHealthyTime: helper.TimeToPtr(12 * time.Hour), 1297 HealthyDeadline: helper.TimeToPtr(12 * time.Hour), 1298 }, 1299 Spreads: []*api.Spread{ 1300 { 1301 Attribute: "${node.datacenter}", 1302 Weight: helper.Int8ToPtr(100), 1303 SpreadTarget: []*api.SpreadTarget{ 1304 { 1305 Value: "dc1", 1306 Percent: 100, 1307 }, 1308 }, 1309 }, 1310 }, 1311 EphemeralDisk: &api.EphemeralDisk{ 1312 SizeMB: helper.IntToPtr(100), 1313 Sticky: helper.BoolToPtr(true), 1314 Migrate: helper.BoolToPtr(true), 1315 }, 1316 Update: &api.UpdateStrategy{ 1317 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), 1318 MinHealthyTime: helper.TimeToPtr(2 * time.Minute), 1319 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 1320 ProgressDeadline: helper.TimeToPtr(5 * time.Minute), 1321 AutoRevert: helper.BoolToPtr(true), 1322 }, 1323 1324 Meta: map[string]string{ 1325 "key": "value", 1326 }, 1327 Tasks: []*api.Task{ 1328 { 1329 Name: "task1", 1330 Leader: true, 1331 Driver: "docker", 1332 User: "mary", 1333 Config: map[string]interface{}{ 1334 "lol": "code", 1335 }, 1336 Env: map[string]string{ 1337 "hello": "world", 1338 }, 1339 Constraints: []*api.Constraint{ 1340 { 1341 LTarget: "x", 1342 RTarget: "y", 1343 Operand: "z", 1344 }, 1345 }, 1346 Affinities: []*api.Affinity{ 1347 { 1348 LTarget: "a", 1349 RTarget: "b", 1350 Operand: "c", 1351 Weight: helper.Int8ToPtr(50), 1352 }, 1353 }, 1354 1355 Services: []*api.Service{ 1356 { 1357 Id: "id", 1358 Name: "serviceA", 1359 Tags: []string{"1", "2"}, 1360 CanaryTags: []string{"3", "4"}, 1361 PortLabel: "foo", 1362 CheckRestart: &api.CheckRestart{ 1363 Limit: 4, 1364 Grace: helper.TimeToPtr(11 * time.Second), 1365 }, 1366 Checks: []api.ServiceCheck{ 1367 { 1368 Id: "hello", 1369 Name: "bar", 1370 Type: "http", 1371 Command: "foo", 1372 Args: []string{"a", "b"}, 1373 Path: "/check", 1374 Protocol: "http", 1375 PortLabel: "foo", 1376 AddressMode: "driver", 1377 GRPCService: "foo.Bar", 1378 GRPCUseTLS: true, 1379 Interval: 4 * time.Second, 1380 Timeout: 2 * time.Second, 1381 InitialStatus: "ok", 1382 CheckRestart: &api.CheckRestart{ 1383 Limit: 3, 1384 IgnoreWarnings: true, 1385 }, 1386 }, 1387 { 1388 Id: "check2id", 1389 Name: "check2", 1390 Type: "tcp", 1391 PortLabel: "foo", 1392 Interval: 4 * time.Second, 1393 Timeout: 2 * time.Second, 1394 }, 1395 }, 1396 }, 1397 }, 1398 Resources: &api.Resources{ 1399 CPU: helper.IntToPtr(100), 1400 MemoryMB: helper.IntToPtr(10), 1401 Networks: []*api.NetworkResource{ 1402 { 1403 IP: "10.10.11.1", 1404 MBits: helper.IntToPtr(10), 1405 ReservedPorts: []api.Port{ 1406 { 1407 Label: "http", 1408 Value: 80, 1409 }, 1410 }, 1411 DynamicPorts: []api.Port{ 1412 { 1413 Label: "ssh", 1414 Value: 2000, 1415 }, 1416 }, 1417 }, 1418 }, 1419 Devices: []*api.RequestedDevice{ 1420 { 1421 Name: "nvidia/gpu", 1422 Count: helper.Uint64ToPtr(4), 1423 Constraints: []*api.Constraint{ 1424 { 1425 LTarget: "x", 1426 RTarget: "y", 1427 Operand: "z", 1428 }, 1429 }, 1430 Affinities: []*api.Affinity{ 1431 { 1432 LTarget: "a", 1433 RTarget: "b", 1434 Operand: "c", 1435 Weight: helper.Int8ToPtr(50), 1436 }, 1437 }, 1438 }, 1439 { 1440 Name: "gpu", 1441 Count: nil, 1442 }, 1443 }, 1444 }, 1445 Meta: map[string]string{ 1446 "lol": "code", 1447 }, 1448 KillTimeout: helper.TimeToPtr(10 * time.Second), 1449 KillSignal: "SIGQUIT", 1450 LogConfig: &api.LogConfig{ 1451 MaxFiles: helper.IntToPtr(10), 1452 MaxFileSizeMB: helper.IntToPtr(100), 1453 }, 1454 Artifacts: []*api.TaskArtifact{ 1455 { 1456 GetterSource: helper.StringToPtr("source"), 1457 GetterOptions: map[string]string{ 1458 "a": "b", 1459 }, 1460 GetterMode: helper.StringToPtr("dir"), 1461 RelativeDest: helper.StringToPtr("dest"), 1462 }, 1463 }, 1464 Vault: &api.Vault{ 1465 Policies: []string{"a", "b", "c"}, 1466 Env: helper.BoolToPtr(true), 1467 ChangeMode: helper.StringToPtr("c"), 1468 ChangeSignal: helper.StringToPtr("sighup"), 1469 }, 1470 Templates: []*api.Template{ 1471 { 1472 SourcePath: helper.StringToPtr("source"), 1473 DestPath: helper.StringToPtr("dest"), 1474 EmbeddedTmpl: helper.StringToPtr("embedded"), 1475 ChangeMode: helper.StringToPtr("change"), 1476 ChangeSignal: helper.StringToPtr("signal"), 1477 Splay: helper.TimeToPtr(1 * time.Minute), 1478 Perms: helper.StringToPtr("666"), 1479 LeftDelim: helper.StringToPtr("abc"), 1480 RightDelim: helper.StringToPtr("def"), 1481 Envvars: helper.BoolToPtr(true), 1482 VaultGrace: helper.TimeToPtr(3 * time.Second), 1483 }, 1484 }, 1485 DispatchPayload: &api.DispatchPayloadConfig{ 1486 File: "fileA", 1487 }, 1488 }, 1489 }, 1490 }, 1491 }, 1492 VaultToken: helper.StringToPtr("token"), 1493 Status: helper.StringToPtr("status"), 1494 StatusDescription: helper.StringToPtr("status_desc"), 1495 Version: helper.Uint64ToPtr(10), 1496 CreateIndex: helper.Uint64ToPtr(1), 1497 ModifyIndex: helper.Uint64ToPtr(3), 1498 JobModifyIndex: helper.Uint64ToPtr(5), 1499 } 1500 1501 expected := &structs.Job{ 1502 Stop: true, 1503 Region: "global", 1504 Namespace: "foo", 1505 ID: "foo", 1506 ParentID: "lol", 1507 Name: "name", 1508 Type: "service", 1509 Priority: 50, 1510 AllAtOnce: true, 1511 Datacenters: []string{"dc1", "dc2"}, 1512 Constraints: []*structs.Constraint{ 1513 { 1514 LTarget: "a", 1515 RTarget: "b", 1516 Operand: "c", 1517 }, 1518 }, 1519 Affinities: []*structs.Affinity{ 1520 { 1521 LTarget: "a", 1522 RTarget: "b", 1523 Operand: "c", 1524 Weight: 50, 1525 }, 1526 }, 1527 Spreads: []*structs.Spread{ 1528 { 1529 Attribute: "${meta.rack}", 1530 Weight: 100, 1531 SpreadTarget: []*structs.SpreadTarget{ 1532 { 1533 Value: "r1", 1534 Percent: 50, 1535 }, 1536 }, 1537 }, 1538 }, 1539 Update: structs.UpdateStrategy{ 1540 Stagger: 1 * time.Second, 1541 MaxParallel: 5, 1542 }, 1543 Periodic: &structs.PeriodicConfig{ 1544 Enabled: true, 1545 Spec: "spec", 1546 SpecType: "cron", 1547 ProhibitOverlap: true, 1548 TimeZone: "test zone", 1549 }, 1550 ParameterizedJob: &structs.ParameterizedJobConfig{ 1551 Payload: "payload", 1552 MetaRequired: []string{"a", "b"}, 1553 MetaOptional: []string{"c", "d"}, 1554 }, 1555 Payload: []byte("payload"), 1556 Meta: map[string]string{ 1557 "foo": "bar", 1558 }, 1559 TaskGroups: []*structs.TaskGroup{ 1560 { 1561 Name: "group1", 1562 Count: 5, 1563 Constraints: []*structs.Constraint{ 1564 { 1565 LTarget: "x", 1566 RTarget: "y", 1567 Operand: "z", 1568 }, 1569 }, 1570 Affinities: []*structs.Affinity{ 1571 { 1572 LTarget: "x", 1573 RTarget: "y", 1574 Operand: "z", 1575 Weight: 100, 1576 }, 1577 }, 1578 RestartPolicy: &structs.RestartPolicy{ 1579 Interval: 1 * time.Second, 1580 Attempts: 5, 1581 Delay: 10 * time.Second, 1582 Mode: "delay", 1583 }, 1584 Spreads: []*structs.Spread{ 1585 { 1586 Attribute: "${node.datacenter}", 1587 Weight: 100, 1588 SpreadTarget: []*structs.SpreadTarget{ 1589 { 1590 Value: "dc1", 1591 Percent: 100, 1592 }, 1593 }, 1594 }, 1595 }, 1596 ReschedulePolicy: &structs.ReschedulePolicy{ 1597 Interval: 12 * time.Hour, 1598 Attempts: 5, 1599 DelayFunction: "constant", 1600 Delay: 30 * time.Second, 1601 Unlimited: true, 1602 MaxDelay: 20 * time.Minute, 1603 }, 1604 Migrate: &structs.MigrateStrategy{ 1605 MaxParallel: 12, 1606 HealthCheck: "task_events", 1607 MinHealthyTime: 12 * time.Hour, 1608 HealthyDeadline: 12 * time.Hour, 1609 }, 1610 EphemeralDisk: &structs.EphemeralDisk{ 1611 SizeMB: 100, 1612 Sticky: true, 1613 Migrate: true, 1614 }, 1615 Update: &structs.UpdateStrategy{ 1616 Stagger: 1 * time.Second, 1617 MaxParallel: 5, 1618 HealthCheck: structs.UpdateStrategyHealthCheck_Checks, 1619 MinHealthyTime: 2 * time.Minute, 1620 HealthyDeadline: 5 * time.Minute, 1621 ProgressDeadline: 5 * time.Minute, 1622 AutoRevert: true, 1623 Canary: 1, 1624 }, 1625 Meta: map[string]string{ 1626 "key": "value", 1627 }, 1628 Tasks: []*structs.Task{ 1629 { 1630 Name: "task1", 1631 Driver: "docker", 1632 Leader: true, 1633 User: "mary", 1634 Config: map[string]interface{}{ 1635 "lol": "code", 1636 }, 1637 Constraints: []*structs.Constraint{ 1638 { 1639 LTarget: "x", 1640 RTarget: "y", 1641 Operand: "z", 1642 }, 1643 }, 1644 Affinities: []*structs.Affinity{ 1645 { 1646 LTarget: "a", 1647 RTarget: "b", 1648 Operand: "c", 1649 Weight: 50, 1650 }, 1651 }, 1652 Env: map[string]string{ 1653 "hello": "world", 1654 }, 1655 Services: []*structs.Service{ 1656 { 1657 Name: "serviceA", 1658 Tags: []string{"1", "2"}, 1659 CanaryTags: []string{"3", "4"}, 1660 PortLabel: "foo", 1661 AddressMode: "auto", 1662 Checks: []*structs.ServiceCheck{ 1663 { 1664 Name: "bar", 1665 Type: "http", 1666 Command: "foo", 1667 Args: []string{"a", "b"}, 1668 Path: "/check", 1669 Protocol: "http", 1670 PortLabel: "foo", 1671 AddressMode: "driver", 1672 Interval: 4 * time.Second, 1673 Timeout: 2 * time.Second, 1674 InitialStatus: "ok", 1675 GRPCService: "foo.Bar", 1676 GRPCUseTLS: true, 1677 CheckRestart: &structs.CheckRestart{ 1678 Limit: 3, 1679 Grace: 11 * time.Second, 1680 IgnoreWarnings: true, 1681 }, 1682 }, 1683 { 1684 Name: "check2", 1685 Type: "tcp", 1686 PortLabel: "foo", 1687 Interval: 4 * time.Second, 1688 Timeout: 2 * time.Second, 1689 CheckRestart: &structs.CheckRestart{ 1690 Limit: 4, 1691 Grace: 11 * time.Second, 1692 }, 1693 }, 1694 }, 1695 }, 1696 }, 1697 Resources: &structs.Resources{ 1698 CPU: 100, 1699 MemoryMB: 10, 1700 Networks: []*structs.NetworkResource{ 1701 { 1702 IP: "10.10.11.1", 1703 MBits: 10, 1704 ReservedPorts: []structs.Port{ 1705 { 1706 Label: "http", 1707 Value: 80, 1708 }, 1709 }, 1710 DynamicPorts: []structs.Port{ 1711 { 1712 Label: "ssh", 1713 Value: 2000, 1714 }, 1715 }, 1716 }, 1717 }, 1718 Devices: []*structs.RequestedDevice{ 1719 { 1720 Name: "nvidia/gpu", 1721 Count: 4, 1722 Constraints: []*structs.Constraint{ 1723 { 1724 LTarget: "x", 1725 RTarget: "y", 1726 Operand: "z", 1727 }, 1728 }, 1729 Affinities: []*structs.Affinity{ 1730 { 1731 LTarget: "a", 1732 RTarget: "b", 1733 Operand: "c", 1734 Weight: 50, 1735 }, 1736 }, 1737 }, 1738 { 1739 Name: "gpu", 1740 Count: 1, 1741 }, 1742 }, 1743 }, 1744 Meta: map[string]string{ 1745 "lol": "code", 1746 }, 1747 KillTimeout: 10 * time.Second, 1748 KillSignal: "SIGQUIT", 1749 LogConfig: &structs.LogConfig{ 1750 MaxFiles: 10, 1751 MaxFileSizeMB: 100, 1752 }, 1753 Artifacts: []*structs.TaskArtifact{ 1754 { 1755 GetterSource: "source", 1756 GetterOptions: map[string]string{ 1757 "a": "b", 1758 }, 1759 GetterMode: "dir", 1760 RelativeDest: "dest", 1761 }, 1762 }, 1763 Vault: &structs.Vault{ 1764 Policies: []string{"a", "b", "c"}, 1765 Env: true, 1766 ChangeMode: "c", 1767 ChangeSignal: "sighup", 1768 }, 1769 Templates: []*structs.Template{ 1770 { 1771 SourcePath: "source", 1772 DestPath: "dest", 1773 EmbeddedTmpl: "embedded", 1774 ChangeMode: "change", 1775 ChangeSignal: "SIGNAL", 1776 Splay: 1 * time.Minute, 1777 Perms: "666", 1778 LeftDelim: "abc", 1779 RightDelim: "def", 1780 Envvars: true, 1781 VaultGrace: 3 * time.Second, 1782 }, 1783 }, 1784 DispatchPayload: &structs.DispatchPayloadConfig{ 1785 File: "fileA", 1786 }, 1787 }, 1788 }, 1789 }, 1790 }, 1791 1792 VaultToken: "token", 1793 } 1794 1795 structsJob := ApiJobToStructJob(apiJob) 1796 1797 if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { 1798 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1799 } 1800 1801 systemAPIJob := &api.Job{ 1802 Stop: helper.BoolToPtr(true), 1803 Region: helper.StringToPtr("global"), 1804 Namespace: helper.StringToPtr("foo"), 1805 ID: helper.StringToPtr("foo"), 1806 ParentID: helper.StringToPtr("lol"), 1807 Name: helper.StringToPtr("name"), 1808 Type: helper.StringToPtr("system"), 1809 Priority: helper.IntToPtr(50), 1810 AllAtOnce: helper.BoolToPtr(true), 1811 Datacenters: []string{"dc1", "dc2"}, 1812 Constraints: []*api.Constraint{ 1813 { 1814 LTarget: "a", 1815 RTarget: "b", 1816 Operand: "c", 1817 }, 1818 }, 1819 TaskGroups: []*api.TaskGroup{ 1820 { 1821 Name: helper.StringToPtr("group1"), 1822 Count: helper.IntToPtr(5), 1823 Constraints: []*api.Constraint{ 1824 { 1825 LTarget: "x", 1826 RTarget: "y", 1827 Operand: "z", 1828 }, 1829 }, 1830 RestartPolicy: &api.RestartPolicy{ 1831 Interval: helper.TimeToPtr(1 * time.Second), 1832 Attempts: helper.IntToPtr(5), 1833 Delay: helper.TimeToPtr(10 * time.Second), 1834 Mode: helper.StringToPtr("delay"), 1835 }, 1836 EphemeralDisk: &api.EphemeralDisk{ 1837 SizeMB: helper.IntToPtr(100), 1838 Sticky: helper.BoolToPtr(true), 1839 Migrate: helper.BoolToPtr(true), 1840 }, 1841 Meta: map[string]string{ 1842 "key": "value", 1843 }, 1844 Tasks: []*api.Task{ 1845 { 1846 Name: "task1", 1847 Leader: true, 1848 Driver: "docker", 1849 User: "mary", 1850 Config: map[string]interface{}{ 1851 "lol": "code", 1852 }, 1853 Env: map[string]string{ 1854 "hello": "world", 1855 }, 1856 Constraints: []*api.Constraint{ 1857 { 1858 LTarget: "x", 1859 RTarget: "y", 1860 Operand: "z", 1861 }, 1862 }, 1863 Resources: &api.Resources{ 1864 CPU: helper.IntToPtr(100), 1865 MemoryMB: helper.IntToPtr(10), 1866 Networks: []*api.NetworkResource{ 1867 { 1868 IP: "10.10.11.1", 1869 MBits: helper.IntToPtr(10), 1870 ReservedPorts: []api.Port{ 1871 { 1872 Label: "http", 1873 Value: 80, 1874 }, 1875 }, 1876 DynamicPorts: []api.Port{ 1877 { 1878 Label: "ssh", 1879 Value: 2000, 1880 }, 1881 }, 1882 }, 1883 }, 1884 }, 1885 Meta: map[string]string{ 1886 "lol": "code", 1887 }, 1888 KillTimeout: helper.TimeToPtr(10 * time.Second), 1889 KillSignal: "SIGQUIT", 1890 LogConfig: &api.LogConfig{ 1891 MaxFiles: helper.IntToPtr(10), 1892 MaxFileSizeMB: helper.IntToPtr(100), 1893 }, 1894 Artifacts: []*api.TaskArtifact{ 1895 { 1896 GetterSource: helper.StringToPtr("source"), 1897 GetterOptions: map[string]string{ 1898 "a": "b", 1899 }, 1900 GetterMode: helper.StringToPtr("dir"), 1901 RelativeDest: helper.StringToPtr("dest"), 1902 }, 1903 }, 1904 DispatchPayload: &api.DispatchPayloadConfig{ 1905 File: "fileA", 1906 }, 1907 }, 1908 }, 1909 }, 1910 }, 1911 Status: helper.StringToPtr("status"), 1912 StatusDescription: helper.StringToPtr("status_desc"), 1913 Version: helper.Uint64ToPtr(10), 1914 CreateIndex: helper.Uint64ToPtr(1), 1915 ModifyIndex: helper.Uint64ToPtr(3), 1916 JobModifyIndex: helper.Uint64ToPtr(5), 1917 } 1918 1919 expectedSystemJob := &structs.Job{ 1920 Stop: true, 1921 Region: "global", 1922 Namespace: "foo", 1923 ID: "foo", 1924 ParentID: "lol", 1925 Name: "name", 1926 Type: "system", 1927 Priority: 50, 1928 AllAtOnce: true, 1929 Datacenters: []string{"dc1", "dc2"}, 1930 Constraints: []*structs.Constraint{ 1931 { 1932 LTarget: "a", 1933 RTarget: "b", 1934 Operand: "c", 1935 }, 1936 }, 1937 TaskGroups: []*structs.TaskGroup{ 1938 { 1939 Name: "group1", 1940 Count: 5, 1941 Constraints: []*structs.Constraint{ 1942 { 1943 LTarget: "x", 1944 RTarget: "y", 1945 Operand: "z", 1946 }, 1947 }, 1948 RestartPolicy: &structs.RestartPolicy{ 1949 Interval: 1 * time.Second, 1950 Attempts: 5, 1951 Delay: 10 * time.Second, 1952 Mode: "delay", 1953 }, 1954 EphemeralDisk: &structs.EphemeralDisk{ 1955 SizeMB: 100, 1956 Sticky: true, 1957 Migrate: true, 1958 }, 1959 Meta: map[string]string{ 1960 "key": "value", 1961 }, 1962 Tasks: []*structs.Task{ 1963 { 1964 Name: "task1", 1965 Driver: "docker", 1966 Leader: true, 1967 User: "mary", 1968 Config: map[string]interface{}{ 1969 "lol": "code", 1970 }, 1971 Constraints: []*structs.Constraint{ 1972 { 1973 LTarget: "x", 1974 RTarget: "y", 1975 Operand: "z", 1976 }, 1977 }, 1978 Env: map[string]string{ 1979 "hello": "world", 1980 }, 1981 Resources: &structs.Resources{ 1982 CPU: 100, 1983 MemoryMB: 10, 1984 Networks: []*structs.NetworkResource{ 1985 { 1986 IP: "10.10.11.1", 1987 MBits: 10, 1988 ReservedPorts: []structs.Port{ 1989 { 1990 Label: "http", 1991 Value: 80, 1992 }, 1993 }, 1994 DynamicPorts: []structs.Port{ 1995 { 1996 Label: "ssh", 1997 Value: 2000, 1998 }, 1999 }, 2000 }, 2001 }, 2002 }, 2003 Meta: map[string]string{ 2004 "lol": "code", 2005 }, 2006 KillTimeout: 10 * time.Second, 2007 KillSignal: "SIGQUIT", 2008 LogConfig: &structs.LogConfig{ 2009 MaxFiles: 10, 2010 MaxFileSizeMB: 100, 2011 }, 2012 Artifacts: []*structs.TaskArtifact{ 2013 { 2014 GetterSource: "source", 2015 GetterOptions: map[string]string{ 2016 "a": "b", 2017 }, 2018 GetterMode: "dir", 2019 RelativeDest: "dest", 2020 }, 2021 }, 2022 DispatchPayload: &structs.DispatchPayloadConfig{ 2023 File: "fileA", 2024 }, 2025 }, 2026 }, 2027 }, 2028 }, 2029 } 2030 2031 systemStructsJob := ApiJobToStructJob(systemAPIJob) 2032 2033 if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 { 2034 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 2035 } 2036 }