github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/command/agent/job_endpoint_test.go (about) 1 package agent 2 3 import ( 4 "net/http" 5 "net/http/httptest" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/golang/snappy" 11 "github.com/hashicorp/nomad/api" 12 "github.com/hashicorp/nomad/helper" 13 "github.com/hashicorp/nomad/nomad/mock" 14 "github.com/hashicorp/nomad/nomad/structs" 15 ) 16 17 func TestHTTP_JobsList(t *testing.T) { 18 httpTest(t, nil, func(s *TestServer) { 19 for i := 0; i < 3; i++ { 20 // Create the job 21 job := mock.Job() 22 args := structs.JobRegisterRequest{ 23 Job: job, 24 WriteRequest: structs.WriteRequest{Region: "global"}, 25 } 26 var resp structs.JobRegisterResponse 27 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 28 t.Fatalf("err: %v", err) 29 } 30 } 31 32 // Make the HTTP request 33 req, err := http.NewRequest("GET", "/v1/jobs", nil) 34 if err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 respW := httptest.NewRecorder() 38 39 // Make the request 40 obj, err := s.Server.JobsRequest(respW, req) 41 if err != nil { 42 t.Fatalf("err: %v", err) 43 } 44 45 // Check for the index 46 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 47 t.Fatalf("missing index") 48 } 49 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 50 t.Fatalf("missing known leader") 51 } 52 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 53 t.Fatalf("missing last contact") 54 } 55 56 // Check the job 57 j := obj.([]*structs.JobListStub) 58 if len(j) != 3 { 59 t.Fatalf("bad: %#v", j) 60 } 61 }) 62 } 63 64 func TestHTTP_PrefixJobsList(t *testing.T) { 65 ids := []string{ 66 "aaaaaaaa-e8f7-fd38-c855-ab94ceb89706", 67 "aabbbbbb-e8f7-fd38-c855-ab94ceb89706", 68 "aabbcccc-e8f7-fd38-c855-ab94ceb89706", 69 } 70 httpTest(t, nil, func(s *TestServer) { 71 for i := 0; i < 3; i++ { 72 // Create the job 73 job := mock.Job() 74 job.ID = ids[i] 75 job.TaskGroups[0].Count = 1 76 args := structs.JobRegisterRequest{ 77 Job: job, 78 WriteRequest: structs.WriteRequest{Region: "global"}, 79 } 80 var resp structs.JobRegisterResponse 81 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 82 t.Fatalf("err: %v", err) 83 } 84 } 85 86 // Make the HTTP request 87 req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil) 88 if err != nil { 89 t.Fatalf("err: %v", err) 90 } 91 respW := httptest.NewRecorder() 92 93 // Make the request 94 obj, err := s.Server.JobsRequest(respW, req) 95 if err != nil { 96 t.Fatalf("err: %v", err) 97 } 98 99 // Check for the index 100 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 101 t.Fatalf("missing index") 102 } 103 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 104 t.Fatalf("missing known leader") 105 } 106 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 107 t.Fatalf("missing last contact") 108 } 109 110 // Check the job 111 j := obj.([]*structs.JobListStub) 112 if len(j) != 2 { 113 t.Fatalf("bad: %#v", j) 114 } 115 }) 116 } 117 118 func TestHTTP_JobsRegister(t *testing.T) { 119 httpTest(t, nil, func(s *TestServer) { 120 // Create the job 121 job := api.MockJob() 122 args := api.JobRegisterRequest{ 123 Job: job, 124 WriteRequest: api.WriteRequest{Region: "global"}, 125 } 126 buf := encodeReq(args) 127 128 // Make the HTTP request 129 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 130 if err != nil { 131 t.Fatalf("err: %v", err) 132 } 133 respW := httptest.NewRecorder() 134 135 // Make the request 136 obj, err := s.Server.JobsRequest(respW, req) 137 if err != nil { 138 t.Fatalf("err: %v", err) 139 } 140 141 // Check the response 142 dereg := obj.(structs.JobRegisterResponse) 143 if dereg.EvalID == "" { 144 t.Fatalf("bad: %v", dereg) 145 } 146 147 // Check for the index 148 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 149 t.Fatalf("missing index") 150 } 151 152 // Check the job is registered 153 getReq := structs.JobSpecificRequest{ 154 JobID: *job.ID, 155 QueryOptions: structs.QueryOptions{Region: "global"}, 156 } 157 var getResp structs.SingleJobResponse 158 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 159 t.Fatalf("err: %v", err) 160 } 161 162 if getResp.Job == nil { 163 t.Fatalf("job does not exist") 164 } 165 }) 166 } 167 168 func TestHTTP_JobsRegister_Defaulting(t *testing.T) { 169 httpTest(t, nil, func(s *TestServer) { 170 // Create the job 171 job := api.MockJob() 172 173 // Do not set its priority 174 job.Priority = nil 175 176 args := api.JobRegisterRequest{ 177 Job: job, 178 WriteRequest: api.WriteRequest{Region: "global"}, 179 } 180 buf := encodeReq(args) 181 182 // Make the HTTP request 183 req, err := http.NewRequest("PUT", "/v1/jobs", buf) 184 if err != nil { 185 t.Fatalf("err: %v", err) 186 } 187 respW := httptest.NewRecorder() 188 189 // Make the request 190 obj, err := s.Server.JobsRequest(respW, req) 191 if err != nil { 192 t.Fatalf("err: %v", err) 193 } 194 195 // Check the response 196 dereg := obj.(structs.JobRegisterResponse) 197 if dereg.EvalID == "" { 198 t.Fatalf("bad: %v", dereg) 199 } 200 201 // Check for the index 202 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 203 t.Fatalf("missing index") 204 } 205 206 // Check the job is registered 207 getReq := structs.JobSpecificRequest{ 208 JobID: *job.ID, 209 QueryOptions: structs.QueryOptions{Region: "global"}, 210 } 211 var getResp structs.SingleJobResponse 212 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 213 t.Fatalf("err: %v", err) 214 } 215 216 if getResp.Job == nil { 217 t.Fatalf("job does not exist") 218 } 219 if getResp.Job.Priority != 50 { 220 t.Fatalf("job didn't get defaulted") 221 } 222 }) 223 } 224 225 func TestHTTP_JobQuery(t *testing.T) { 226 httpTest(t, nil, func(s *TestServer) { 227 // Create the job 228 job := mock.Job() 229 args := structs.JobRegisterRequest{ 230 Job: job, 231 WriteRequest: structs.WriteRequest{Region: "global"}, 232 } 233 var resp structs.JobRegisterResponse 234 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 235 t.Fatalf("err: %v", err) 236 } 237 238 // Make the HTTP request 239 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 240 if err != nil { 241 t.Fatalf("err: %v", err) 242 } 243 respW := httptest.NewRecorder() 244 245 // Make the request 246 obj, err := s.Server.JobSpecificRequest(respW, req) 247 if err != nil { 248 t.Fatalf("err: %v", err) 249 } 250 251 // Check for the index 252 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 253 t.Fatalf("missing index") 254 } 255 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 256 t.Fatalf("missing known leader") 257 } 258 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 259 t.Fatalf("missing last contact") 260 } 261 262 // Check the job 263 j := obj.(*structs.Job) 264 if j.ID != job.ID { 265 t.Fatalf("bad: %#v", j) 266 } 267 }) 268 } 269 270 func TestHTTP_JobQuery_Payload(t *testing.T) { 271 httpTest(t, nil, func(s *TestServer) { 272 // Create the job 273 job := mock.Job() 274 275 // Insert Payload compressed 276 expected := []byte("hello world") 277 compressed := snappy.Encode(nil, expected) 278 job.Payload = compressed 279 280 // Directly manipulate the state 281 state := s.Agent.server.State() 282 if err := state.UpsertJob(1000, job); err != nil { 283 t.Fatalf("Failed to upsert job: %v", err) 284 } 285 286 // Make the HTTP request 287 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 288 if err != nil { 289 t.Fatalf("err: %v", err) 290 } 291 respW := httptest.NewRecorder() 292 293 // Make the request 294 obj, err := s.Server.JobSpecificRequest(respW, req) 295 if err != nil { 296 t.Fatalf("err: %v", err) 297 } 298 299 // Check for the index 300 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 301 t.Fatalf("missing index") 302 } 303 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 304 t.Fatalf("missing known leader") 305 } 306 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 307 t.Fatalf("missing last contact") 308 } 309 310 // Check the job 311 j := obj.(*structs.Job) 312 if j.ID != job.ID { 313 t.Fatalf("bad: %#v", j) 314 } 315 316 // Check the payload is decompressed 317 if !reflect.DeepEqual(j.Payload, expected) { 318 t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected) 319 } 320 }) 321 } 322 323 func TestHTTP_JobUpdate(t *testing.T) { 324 httpTest(t, nil, func(s *TestServer) { 325 // Create the job 326 job := api.MockJob() 327 args := api.JobRegisterRequest{ 328 Job: job, 329 WriteRequest: api.WriteRequest{Region: "global"}, 330 } 331 buf := encodeReq(args) 332 333 // Make the HTTP request 334 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf) 335 if err != nil { 336 t.Fatalf("err: %v", err) 337 } 338 respW := httptest.NewRecorder() 339 340 // Make the request 341 obj, err := s.Server.JobSpecificRequest(respW, req) 342 if err != nil { 343 t.Fatalf("err: %v", err) 344 } 345 346 // Check the response 347 dereg := obj.(structs.JobRegisterResponse) 348 if dereg.EvalID == "" { 349 t.Fatalf("bad: %v", dereg) 350 } 351 352 // Check for the index 353 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 354 t.Fatalf("missing index") 355 } 356 357 // Check the job is registered 358 getReq := structs.JobSpecificRequest{ 359 JobID: *job.ID, 360 QueryOptions: structs.QueryOptions{Region: "global"}, 361 } 362 var getResp structs.SingleJobResponse 363 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 364 t.Fatalf("err: %v", err) 365 } 366 367 if getResp.Job == nil { 368 t.Fatalf("job does not exist") 369 } 370 }) 371 } 372 373 func TestHTTP_JobDelete(t *testing.T) { 374 httpTest(t, nil, func(s *TestServer) { 375 // Create the job 376 job := mock.Job() 377 args := structs.JobRegisterRequest{ 378 Job: job, 379 WriteRequest: structs.WriteRequest{Region: "global"}, 380 } 381 var resp structs.JobRegisterResponse 382 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 383 t.Fatalf("err: %v", err) 384 } 385 386 // Make the HTTP request to do a soft delete 387 req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil) 388 if err != nil { 389 t.Fatalf("err: %v", err) 390 } 391 respW := httptest.NewRecorder() 392 393 // Make the request 394 obj, err := s.Server.JobSpecificRequest(respW, req) 395 if err != nil { 396 t.Fatalf("err: %v", err) 397 } 398 399 // Check the response 400 dereg := obj.(structs.JobDeregisterResponse) 401 if dereg.EvalID == "" { 402 t.Fatalf("bad: %v", dereg) 403 } 404 405 // Check for the index 406 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 407 t.Fatalf("missing index") 408 } 409 410 // Check the job is still queryable 411 getReq1 := structs.JobSpecificRequest{ 412 JobID: job.ID, 413 QueryOptions: structs.QueryOptions{Region: "global"}, 414 } 415 var getResp1 structs.SingleJobResponse 416 if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil { 417 t.Fatalf("err: %v", err) 418 } 419 if getResp1.Job == nil { 420 t.Fatalf("job doesn't exists") 421 } 422 if !getResp1.Job.Stop { 423 t.Fatalf("job should be marked as stop") 424 } 425 426 // Make the HTTP request to do a purge delete 427 req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil) 428 if err != nil { 429 t.Fatalf("err: %v", err) 430 } 431 respW.Flush() 432 433 // Make the request 434 obj, err = s.Server.JobSpecificRequest(respW, req2) 435 if err != nil { 436 t.Fatalf("err: %v", err) 437 } 438 439 // Check the response 440 dereg = obj.(structs.JobDeregisterResponse) 441 if dereg.EvalID == "" { 442 t.Fatalf("bad: %v", dereg) 443 } 444 445 // Check for the index 446 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 447 t.Fatalf("missing index") 448 } 449 450 // Check the job is gone 451 getReq2 := structs.JobSpecificRequest{ 452 JobID: job.ID, 453 QueryOptions: structs.QueryOptions{Region: "global"}, 454 } 455 var getResp2 structs.SingleJobResponse 456 if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil { 457 t.Fatalf("err: %v", err) 458 } 459 if getResp2.Job != nil { 460 t.Fatalf("job still exists") 461 } 462 }) 463 } 464 465 func TestHTTP_JobForceEvaluate(t *testing.T) { 466 httpTest(t, nil, func(s *TestServer) { 467 // Create the job 468 job := mock.Job() 469 args := structs.JobRegisterRequest{ 470 Job: job, 471 WriteRequest: structs.WriteRequest{Region: "global"}, 472 } 473 var resp structs.JobRegisterResponse 474 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 475 t.Fatalf("err: %v", err) 476 } 477 478 // Make the HTTP request 479 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil) 480 if err != nil { 481 t.Fatalf("err: %v", err) 482 } 483 respW := httptest.NewRecorder() 484 485 // Make the request 486 obj, err := s.Server.JobSpecificRequest(respW, req) 487 if err != nil { 488 t.Fatalf("err: %v", err) 489 } 490 491 // Check the response 492 reg := obj.(structs.JobRegisterResponse) 493 if reg.EvalID == "" { 494 t.Fatalf("bad: %v", reg) 495 } 496 497 // Check for the index 498 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 499 t.Fatalf("missing index") 500 } 501 }) 502 } 503 504 func TestHTTP_JobEvaluations(t *testing.T) { 505 httpTest(t, nil, func(s *TestServer) { 506 // Create the job 507 job := mock.Job() 508 args := structs.JobRegisterRequest{ 509 Job: job, 510 WriteRequest: structs.WriteRequest{Region: "global"}, 511 } 512 var resp structs.JobRegisterResponse 513 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 514 t.Fatalf("err: %v", err) 515 } 516 517 // Make the HTTP request 518 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil) 519 if err != nil { 520 t.Fatalf("err: %v", err) 521 } 522 respW := httptest.NewRecorder() 523 524 // Make the request 525 obj, err := s.Server.JobSpecificRequest(respW, req) 526 if err != nil { 527 t.Fatalf("err: %v", err) 528 } 529 530 // Check the response 531 evals := obj.([]*structs.Evaluation) 532 // Can be multiple evals, use the last one, since they are in order 533 idx := len(evals) - 1 534 if len(evals) < 0 || evals[idx].ID != resp.EvalID { 535 t.Fatalf("bad: %v", evals) 536 } 537 538 // Check for the index 539 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 540 t.Fatalf("missing index") 541 } 542 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 543 t.Fatalf("missing known leader") 544 } 545 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 546 t.Fatalf("missing last contact") 547 } 548 }) 549 } 550 551 func TestHTTP_JobAllocations(t *testing.T) { 552 httpTest(t, nil, func(s *TestServer) { 553 // Create the job 554 alloc1 := mock.Alloc() 555 args := structs.JobRegisterRequest{ 556 Job: alloc1.Job, 557 WriteRequest: structs.WriteRequest{Region: "global"}, 558 } 559 var resp structs.JobRegisterResponse 560 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 561 t.Fatalf("err: %v", err) 562 } 563 564 // Directly manipulate the state 565 state := s.Agent.server.State() 566 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 567 if err != nil { 568 t.Fatalf("err: %v", err) 569 } 570 571 // Make the HTTP request 572 req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil) 573 if err != nil { 574 t.Fatalf("err: %v", err) 575 } 576 respW := httptest.NewRecorder() 577 578 // Make the request 579 obj, err := s.Server.JobSpecificRequest(respW, req) 580 if err != nil { 581 t.Fatalf("err: %v", err) 582 } 583 584 // Check the response 585 allocs := obj.([]*structs.AllocListStub) 586 if len(allocs) != 1 && allocs[0].ID != alloc1.ID { 587 t.Fatalf("bad: %v", allocs) 588 } 589 590 // Check for the index 591 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 592 t.Fatalf("missing index") 593 } 594 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 595 t.Fatalf("missing known leader") 596 } 597 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 598 t.Fatalf("missing last contact") 599 } 600 }) 601 } 602 603 func TestHTTP_JobVersions(t *testing.T) { 604 httpTest(t, nil, func(s *TestServer) { 605 // Create the job 606 job := mock.Job() 607 args := structs.JobRegisterRequest{ 608 Job: job, 609 WriteRequest: structs.WriteRequest{Region: "global"}, 610 } 611 var resp structs.JobRegisterResponse 612 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 613 t.Fatalf("err: %v", err) 614 } 615 616 job2 := mock.Job() 617 job2.ID = job.ID 618 job2.Priority = 100 619 620 args2 := structs.JobRegisterRequest{ 621 Job: job2, 622 WriteRequest: structs.WriteRequest{Region: "global"}, 623 } 624 var resp2 structs.JobRegisterResponse 625 if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil { 626 t.Fatalf("err: %v", err) 627 } 628 629 // Make the HTTP request 630 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions", nil) 631 if err != nil { 632 t.Fatalf("err: %v", err) 633 } 634 respW := httptest.NewRecorder() 635 636 // Make the request 637 obj, err := s.Server.JobSpecificRequest(respW, req) 638 if err != nil { 639 t.Fatalf("err: %v", err) 640 } 641 642 // Check the response 643 versions := obj.([]*structs.Job) 644 if len(versions) != 2 { 645 t.Fatalf("got %d versions; want 2", len(versions)) 646 } 647 648 if v := versions[0]; v.Version != 1 || v.Priority != 100 { 649 t.Fatalf("bad %v", v) 650 } 651 652 if v := versions[1]; v.Version != 0 { 653 t.Fatalf("bad %v", v) 654 } 655 656 // Check for the index 657 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 658 t.Fatalf("missing index") 659 } 660 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 661 t.Fatalf("missing known leader") 662 } 663 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 664 t.Fatalf("missing last contact") 665 } 666 }) 667 } 668 669 func TestHTTP_PeriodicForce(t *testing.T) { 670 httpTest(t, nil, func(s *TestServer) { 671 // Create and register a periodic job. 672 job := mock.PeriodicJob() 673 args := structs.JobRegisterRequest{ 674 Job: job, 675 WriteRequest: structs.WriteRequest{Region: "global"}, 676 } 677 var resp structs.JobRegisterResponse 678 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 679 t.Fatalf("err: %v", err) 680 } 681 682 // Make the HTTP request 683 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) 684 if err != nil { 685 t.Fatalf("err: %v", err) 686 } 687 respW := httptest.NewRecorder() 688 689 // Make the request 690 obj, err := s.Server.JobSpecificRequest(respW, req) 691 if err != nil { 692 t.Fatalf("err: %v", err) 693 } 694 695 // Check for the index 696 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 697 t.Fatalf("missing index") 698 } 699 700 // Check the response 701 r := obj.(structs.PeriodicForceResponse) 702 if r.EvalID == "" { 703 t.Fatalf("bad: %#v", r) 704 } 705 }) 706 } 707 708 func TestHTTP_JobPlan(t *testing.T) { 709 httpTest(t, nil, func(s *TestServer) { 710 // Create the job 711 job := mock.Job() 712 args := structs.JobPlanRequest{ 713 Job: job, 714 Diff: true, 715 WriteRequest: structs.WriteRequest{Region: "global"}, 716 } 717 buf := encodeReq(args) 718 719 // Make the HTTP request 720 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/plan", buf) 721 if err != nil { 722 t.Fatalf("err: %v", err) 723 } 724 respW := httptest.NewRecorder() 725 726 // Make the request 727 obj, err := s.Server.JobSpecificRequest(respW, req) 728 if err != nil { 729 t.Fatalf("err: %v", err) 730 } 731 732 // Check the response 733 plan := obj.(structs.JobPlanResponse) 734 if plan.Annotations == nil { 735 t.Fatalf("bad: %v", plan) 736 } 737 738 if plan.Diff == nil { 739 t.Fatalf("bad: %v", plan) 740 } 741 }) 742 } 743 744 func TestHTTP_JobDispatch(t *testing.T) { 745 httpTest(t, nil, func(s *TestServer) { 746 // Create the parameterized job 747 job := mock.Job() 748 job.Type = "batch" 749 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 750 751 args := structs.JobRegisterRequest{ 752 Job: job, 753 WriteRequest: structs.WriteRequest{Region: "global"}, 754 } 755 var resp structs.JobRegisterResponse 756 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 757 t.Fatalf("err: %v", err) 758 } 759 760 // Make the request 761 respW := httptest.NewRecorder() 762 args2 := structs.JobDispatchRequest{ 763 WriteRequest: structs.WriteRequest{Region: "global"}, 764 } 765 buf := encodeReq(args2) 766 767 // Make the HTTP request 768 req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) 769 if err != nil { 770 t.Fatalf("err: %v", err) 771 } 772 respW.Flush() 773 774 // Make the request 775 obj, err := s.Server.JobSpecificRequest(respW, req2) 776 if err != nil { 777 t.Fatalf("err: %v", err) 778 } 779 780 // Check the response 781 dispatch := obj.(structs.JobDispatchResponse) 782 if dispatch.EvalID == "" { 783 t.Fatalf("bad: %v", dispatch) 784 } 785 786 if dispatch.DispatchedJobID == "" { 787 t.Fatalf("bad: %v", dispatch) 788 } 789 }) 790 } 791 792 func TestHTTP_JobRevert(t *testing.T) { 793 httpTest(t, nil, func(s *TestServer) { 794 // Create the job and register it twice 795 job := mock.Job() 796 regReq := structs.JobRegisterRequest{ 797 Job: job, 798 WriteRequest: structs.WriteRequest{Region: "global"}, 799 } 800 var regResp structs.JobRegisterResponse 801 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 802 t.Fatalf("err: %v", err) 803 } 804 805 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 806 t.Fatalf("err: %v", err) 807 } 808 809 args := structs.JobRevertRequest{ 810 JobID: job.ID, 811 JobVersion: 0, 812 WriteRequest: structs.WriteRequest{Region: "global"}, 813 } 814 buf := encodeReq(args) 815 816 // Make the HTTP request 817 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) 818 if err != nil { 819 t.Fatalf("err: %v", err) 820 } 821 respW := httptest.NewRecorder() 822 823 // Make the request 824 obj, err := s.Server.JobSpecificRequest(respW, req) 825 if err != nil { 826 t.Fatalf("err: %v", err) 827 } 828 829 // Check the response 830 revertResp := obj.(structs.JobRegisterResponse) 831 if revertResp.EvalID == "" { 832 t.Fatalf("bad: %v", revertResp) 833 } 834 835 // Check for the index 836 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 837 t.Fatalf("missing index") 838 } 839 }) 840 } 841 842 func TestJobs_ApiJobToStructsJob(t *testing.T) { 843 apiJob := &api.Job{ 844 Stop: helper.BoolToPtr(true), 845 Region: helper.StringToPtr("global"), 846 ID: helper.StringToPtr("foo"), 847 ParentID: helper.StringToPtr("lol"), 848 Name: helper.StringToPtr("name"), 849 Type: helper.StringToPtr("service"), 850 Priority: helper.IntToPtr(50), 851 AllAtOnce: helper.BoolToPtr(true), 852 Datacenters: []string{"dc1", "dc2"}, 853 Constraints: []*api.Constraint{ 854 { 855 LTarget: "a", 856 RTarget: "b", 857 Operand: "c", 858 }, 859 }, 860 Update: &api.UpdateStrategy{ 861 Stagger: 1 * time.Second, 862 MaxParallel: 5, 863 }, 864 Periodic: &api.PeriodicConfig{ 865 Enabled: helper.BoolToPtr(true), 866 Spec: helper.StringToPtr("spec"), 867 SpecType: helper.StringToPtr("cron"), 868 ProhibitOverlap: helper.BoolToPtr(true), 869 TimeZone: helper.StringToPtr("test zone"), 870 }, 871 ParameterizedJob: &api.ParameterizedJobConfig{ 872 Payload: "payload", 873 MetaRequired: []string{"a", "b"}, 874 MetaOptional: []string{"c", "d"}, 875 }, 876 Payload: []byte("payload"), 877 Meta: map[string]string{ 878 "foo": "bar", 879 }, 880 TaskGroups: []*api.TaskGroup{ 881 { 882 Name: helper.StringToPtr("group1"), 883 Count: helper.IntToPtr(5), 884 Constraints: []*api.Constraint{ 885 { 886 LTarget: "x", 887 RTarget: "y", 888 Operand: "z", 889 }, 890 }, 891 RestartPolicy: &api.RestartPolicy{ 892 Interval: helper.TimeToPtr(1 * time.Second), 893 Attempts: helper.IntToPtr(5), 894 Delay: helper.TimeToPtr(10 * time.Second), 895 Mode: helper.StringToPtr("delay"), 896 }, 897 EphemeralDisk: &api.EphemeralDisk{ 898 SizeMB: helper.IntToPtr(100), 899 Sticky: helper.BoolToPtr(true), 900 Migrate: helper.BoolToPtr(true), 901 }, 902 Meta: map[string]string{ 903 "key": "value", 904 }, 905 Tasks: []*api.Task{ 906 { 907 Name: "task1", 908 Leader: true, 909 Driver: "docker", 910 User: "mary", 911 Config: map[string]interface{}{ 912 "lol": "code", 913 }, 914 Env: map[string]string{ 915 "hello": "world", 916 }, 917 Constraints: []*api.Constraint{ 918 { 919 LTarget: "x", 920 RTarget: "y", 921 Operand: "z", 922 }, 923 }, 924 925 Services: []*api.Service{ 926 { 927 Id: "id", 928 Name: "serviceA", 929 Tags: []string{"1", "2"}, 930 PortLabel: "foo", 931 Checks: []api.ServiceCheck{ 932 { 933 Id: "hello", 934 Name: "bar", 935 Type: "http", 936 Command: "foo", 937 Args: []string{"a", "b"}, 938 Path: "/check", 939 Protocol: "http", 940 PortLabel: "foo", 941 Interval: 4 * time.Second, 942 Timeout: 2 * time.Second, 943 InitialStatus: "ok", 944 }, 945 }, 946 }, 947 }, 948 Resources: &api.Resources{ 949 CPU: helper.IntToPtr(100), 950 MemoryMB: helper.IntToPtr(10), 951 Networks: []*api.NetworkResource{ 952 { 953 IP: "10.10.11.1", 954 MBits: helper.IntToPtr(10), 955 ReservedPorts: []api.Port{ 956 { 957 Label: "http", 958 Value: 80, 959 }, 960 }, 961 DynamicPorts: []api.Port{ 962 { 963 Label: "ssh", 964 Value: 2000, 965 }, 966 }, 967 }, 968 }, 969 }, 970 Meta: map[string]string{ 971 "lol": "code", 972 }, 973 KillTimeout: helper.TimeToPtr(10 * time.Second), 974 LogConfig: &api.LogConfig{ 975 MaxFiles: helper.IntToPtr(10), 976 MaxFileSizeMB: helper.IntToPtr(100), 977 }, 978 Artifacts: []*api.TaskArtifact{ 979 { 980 GetterSource: helper.StringToPtr("source"), 981 GetterOptions: map[string]string{ 982 "a": "b", 983 }, 984 RelativeDest: helper.StringToPtr("dest"), 985 }, 986 }, 987 Vault: &api.Vault{ 988 Policies: []string{"a", "b", "c"}, 989 Env: helper.BoolToPtr(true), 990 ChangeMode: helper.StringToPtr("c"), 991 ChangeSignal: helper.StringToPtr("sighup"), 992 }, 993 Templates: []*api.Template{ 994 { 995 SourcePath: helper.StringToPtr("source"), 996 DestPath: helper.StringToPtr("dest"), 997 EmbeddedTmpl: helper.StringToPtr("embedded"), 998 ChangeMode: helper.StringToPtr("change"), 999 ChangeSignal: helper.StringToPtr("signal"), 1000 Splay: helper.TimeToPtr(1 * time.Minute), 1001 Perms: helper.StringToPtr("666"), 1002 LeftDelim: helper.StringToPtr("abc"), 1003 RightDelim: helper.StringToPtr("def"), 1004 }, 1005 }, 1006 DispatchPayload: &api.DispatchPayloadConfig{ 1007 File: "fileA", 1008 }, 1009 }, 1010 }, 1011 }, 1012 }, 1013 VaultToken: helper.StringToPtr("token"), 1014 Status: helper.StringToPtr("status"), 1015 StatusDescription: helper.StringToPtr("status_desc"), 1016 Version: helper.Uint64ToPtr(10), 1017 CreateIndex: helper.Uint64ToPtr(1), 1018 ModifyIndex: helper.Uint64ToPtr(3), 1019 JobModifyIndex: helper.Uint64ToPtr(5), 1020 } 1021 1022 expected := &structs.Job{ 1023 Stop: true, 1024 Region: "global", 1025 ID: "foo", 1026 ParentID: "lol", 1027 Name: "name", 1028 Type: "service", 1029 Priority: 50, 1030 AllAtOnce: true, 1031 Datacenters: []string{"dc1", "dc2"}, 1032 Constraints: []*structs.Constraint{ 1033 { 1034 LTarget: "a", 1035 RTarget: "b", 1036 Operand: "c", 1037 }, 1038 }, 1039 Update: structs.UpdateStrategy{ 1040 Stagger: 1 * time.Second, 1041 MaxParallel: 5, 1042 }, 1043 Periodic: &structs.PeriodicConfig{ 1044 Enabled: true, 1045 Spec: "spec", 1046 SpecType: "cron", 1047 ProhibitOverlap: true, 1048 TimeZone: "test zone", 1049 }, 1050 ParameterizedJob: &structs.ParameterizedJobConfig{ 1051 Payload: "payload", 1052 MetaRequired: []string{"a", "b"}, 1053 MetaOptional: []string{"c", "d"}, 1054 }, 1055 Payload: []byte("payload"), 1056 Meta: map[string]string{ 1057 "foo": "bar", 1058 }, 1059 TaskGroups: []*structs.TaskGroup{ 1060 { 1061 Name: "group1", 1062 Count: 5, 1063 Constraints: []*structs.Constraint{ 1064 { 1065 LTarget: "x", 1066 RTarget: "y", 1067 Operand: "z", 1068 }, 1069 }, 1070 RestartPolicy: &structs.RestartPolicy{ 1071 Interval: 1 * time.Second, 1072 Attempts: 5, 1073 Delay: 10 * time.Second, 1074 Mode: "delay", 1075 }, 1076 EphemeralDisk: &structs.EphemeralDisk{ 1077 SizeMB: 100, 1078 Sticky: true, 1079 Migrate: true, 1080 }, 1081 Meta: map[string]string{ 1082 "key": "value", 1083 }, 1084 Tasks: []*structs.Task{ 1085 { 1086 Name: "task1", 1087 Driver: "docker", 1088 Leader: true, 1089 User: "mary", 1090 Config: map[string]interface{}{ 1091 "lol": "code", 1092 }, 1093 Constraints: []*structs.Constraint{ 1094 { 1095 LTarget: "x", 1096 RTarget: "y", 1097 Operand: "z", 1098 }, 1099 }, 1100 Env: map[string]string{ 1101 "hello": "world", 1102 }, 1103 Services: []*structs.Service{ 1104 &structs.Service{ 1105 Name: "serviceA", 1106 Tags: []string{"1", "2"}, 1107 PortLabel: "foo", 1108 Checks: []*structs.ServiceCheck{ 1109 &structs.ServiceCheck{ 1110 Name: "bar", 1111 Type: "http", 1112 Command: "foo", 1113 Args: []string{"a", "b"}, 1114 Path: "/check", 1115 Protocol: "http", 1116 PortLabel: "foo", 1117 Interval: 4 * time.Second, 1118 Timeout: 2 * time.Second, 1119 InitialStatus: "ok", 1120 }, 1121 }, 1122 }, 1123 }, 1124 Resources: &structs.Resources{ 1125 CPU: 100, 1126 MemoryMB: 10, 1127 Networks: []*structs.NetworkResource{ 1128 { 1129 IP: "10.10.11.1", 1130 MBits: 10, 1131 ReservedPorts: []structs.Port{ 1132 { 1133 Label: "http", 1134 Value: 80, 1135 }, 1136 }, 1137 DynamicPorts: []structs.Port{ 1138 { 1139 Label: "ssh", 1140 Value: 2000, 1141 }, 1142 }, 1143 }, 1144 }, 1145 }, 1146 Meta: map[string]string{ 1147 "lol": "code", 1148 }, 1149 KillTimeout: 10 * time.Second, 1150 LogConfig: &structs.LogConfig{ 1151 MaxFiles: 10, 1152 MaxFileSizeMB: 100, 1153 }, 1154 Artifacts: []*structs.TaskArtifact{ 1155 { 1156 GetterSource: "source", 1157 GetterOptions: map[string]string{ 1158 "a": "b", 1159 }, 1160 RelativeDest: "dest", 1161 }, 1162 }, 1163 Vault: &structs.Vault{ 1164 Policies: []string{"a", "b", "c"}, 1165 Env: true, 1166 ChangeMode: "c", 1167 ChangeSignal: "sighup", 1168 }, 1169 Templates: []*structs.Template{ 1170 { 1171 SourcePath: "source", 1172 DestPath: "dest", 1173 EmbeddedTmpl: "embedded", 1174 ChangeMode: "change", 1175 ChangeSignal: "SIGNAL", 1176 Splay: 1 * time.Minute, 1177 Perms: "666", 1178 LeftDelim: "abc", 1179 RightDelim: "def", 1180 }, 1181 }, 1182 DispatchPayload: &structs.DispatchPayloadConfig{ 1183 File: "fileA", 1184 }, 1185 }, 1186 }, 1187 }, 1188 }, 1189 1190 VaultToken: "token", 1191 } 1192 1193 structsJob := ApiJobToStructJob(apiJob) 1194 1195 if !reflect.DeepEqual(expected, structsJob) { 1196 t.Fatalf("bad %#v", structsJob) 1197 } 1198 }