github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/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_JobQuery(t *testing.T) { 276 t.Parallel() 277 httpTest(t, nil, func(s *TestAgent) { 278 // Create the job 279 job := mock.Job() 280 args := structs.JobRegisterRequest{ 281 Job: job, 282 WriteRequest: structs.WriteRequest{ 283 Region: "global", 284 Namespace: structs.DefaultNamespace, 285 }, 286 } 287 var resp structs.JobRegisterResponse 288 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 289 t.Fatalf("err: %v", err) 290 } 291 292 // Make the HTTP request 293 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 294 if err != nil { 295 t.Fatalf("err: %v", err) 296 } 297 respW := httptest.NewRecorder() 298 299 // Make the request 300 obj, err := s.Server.JobSpecificRequest(respW, req) 301 if err != nil { 302 t.Fatalf("err: %v", err) 303 } 304 305 // Check for the index 306 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 307 t.Fatalf("missing index") 308 } 309 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 310 t.Fatalf("missing known leader") 311 } 312 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 313 t.Fatalf("missing last contact") 314 } 315 316 // Check the job 317 j := obj.(*structs.Job) 318 if j.ID != job.ID { 319 t.Fatalf("bad: %#v", j) 320 } 321 }) 322 } 323 324 func TestHTTP_JobQuery_Payload(t *testing.T) { 325 t.Parallel() 326 httpTest(t, nil, func(s *TestAgent) { 327 // Create the job 328 job := mock.Job() 329 330 // Insert Payload compressed 331 expected := []byte("hello world") 332 compressed := snappy.Encode(nil, expected) 333 job.Payload = compressed 334 335 // Directly manipulate the state 336 state := s.Agent.server.State() 337 if err := state.UpsertJob(1000, job); err != nil { 338 t.Fatalf("Failed to upsert job: %v", err) 339 } 340 341 // Make the HTTP request 342 req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) 343 if err != nil { 344 t.Fatalf("err: %v", err) 345 } 346 respW := httptest.NewRecorder() 347 348 // Make the request 349 obj, err := s.Server.JobSpecificRequest(respW, req) 350 if err != nil { 351 t.Fatalf("err: %v", err) 352 } 353 354 // Check for the index 355 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 356 t.Fatalf("missing index") 357 } 358 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 359 t.Fatalf("missing known leader") 360 } 361 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 362 t.Fatalf("missing last contact") 363 } 364 365 // Check the job 366 j := obj.(*structs.Job) 367 if j.ID != job.ID { 368 t.Fatalf("bad: %#v", j) 369 } 370 371 // Check the payload is decompressed 372 if !reflect.DeepEqual(j.Payload, expected) { 373 t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected) 374 } 375 }) 376 } 377 378 func TestHTTP_JobUpdate(t *testing.T) { 379 t.Parallel() 380 httpTest(t, nil, func(s *TestAgent) { 381 // Create the job 382 job := api.MockJob() 383 args := api.JobRegisterRequest{ 384 Job: job, 385 WriteRequest: api.WriteRequest{ 386 Region: "global", 387 Namespace: api.DefaultNamespace, 388 }, 389 } 390 buf := encodeReq(args) 391 392 // Make the HTTP request 393 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf) 394 if err != nil { 395 t.Fatalf("err: %v", err) 396 } 397 respW := httptest.NewRecorder() 398 399 // Make the request 400 obj, err := s.Server.JobSpecificRequest(respW, req) 401 if err != nil { 402 t.Fatalf("err: %v", err) 403 } 404 405 // Check the response 406 dereg := obj.(structs.JobRegisterResponse) 407 if dereg.EvalID == "" { 408 t.Fatalf("bad: %v", dereg) 409 } 410 411 // Check for the index 412 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 413 t.Fatalf("missing index") 414 } 415 416 // Check the job is registered 417 getReq := structs.JobSpecificRequest{ 418 JobID: *job.ID, 419 QueryOptions: structs.QueryOptions{ 420 Region: "global", 421 Namespace: structs.DefaultNamespace, 422 }, 423 } 424 var getResp structs.SingleJobResponse 425 if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { 426 t.Fatalf("err: %v", err) 427 } 428 429 if getResp.Job == nil { 430 t.Fatalf("job does not exist") 431 } 432 }) 433 } 434 435 func TestHTTP_JobDelete(t *testing.T) { 436 t.Parallel() 437 httpTest(t, nil, func(s *TestAgent) { 438 // Create the job 439 job := mock.Job() 440 args := structs.JobRegisterRequest{ 441 Job: job, 442 WriteRequest: structs.WriteRequest{ 443 Region: "global", 444 Namespace: structs.DefaultNamespace, 445 }, 446 } 447 var resp structs.JobRegisterResponse 448 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 449 t.Fatalf("err: %v", err) 450 } 451 452 // Make the HTTP request to do a soft delete 453 req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil) 454 if err != nil { 455 t.Fatalf("err: %v", err) 456 } 457 respW := httptest.NewRecorder() 458 459 // Make the request 460 obj, err := s.Server.JobSpecificRequest(respW, req) 461 if err != nil { 462 t.Fatalf("err: %v", err) 463 } 464 465 // Check the response 466 dereg := obj.(structs.JobDeregisterResponse) 467 if dereg.EvalID == "" { 468 t.Fatalf("bad: %v", dereg) 469 } 470 471 // Check for the index 472 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 473 t.Fatalf("missing index") 474 } 475 476 // Check the job is still queryable 477 getReq1 := structs.JobSpecificRequest{ 478 JobID: job.ID, 479 QueryOptions: structs.QueryOptions{ 480 Region: "global", 481 Namespace: structs.DefaultNamespace, 482 }, 483 } 484 var getResp1 structs.SingleJobResponse 485 if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil { 486 t.Fatalf("err: %v", err) 487 } 488 if getResp1.Job == nil { 489 t.Fatalf("job doesn't exists") 490 } 491 if !getResp1.Job.Stop { 492 t.Fatalf("job should be marked as stop") 493 } 494 495 // Make the HTTP request to do a purge delete 496 req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil) 497 if err != nil { 498 t.Fatalf("err: %v", err) 499 } 500 respW.Flush() 501 502 // Make the request 503 obj, err = s.Server.JobSpecificRequest(respW, req2) 504 if err != nil { 505 t.Fatalf("err: %v", err) 506 } 507 508 // Check the response 509 dereg = obj.(structs.JobDeregisterResponse) 510 if dereg.EvalID == "" { 511 t.Fatalf("bad: %v", dereg) 512 } 513 514 // Check for the index 515 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 516 t.Fatalf("missing index") 517 } 518 519 // Check the job is gone 520 getReq2 := structs.JobSpecificRequest{ 521 JobID: job.ID, 522 QueryOptions: structs.QueryOptions{ 523 Region: "global", 524 Namespace: structs.DefaultNamespace, 525 }, 526 } 527 var getResp2 structs.SingleJobResponse 528 if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil { 529 t.Fatalf("err: %v", err) 530 } 531 if getResp2.Job != nil { 532 t.Fatalf("job still exists") 533 } 534 }) 535 } 536 537 func TestHTTP_JobForceEvaluate(t *testing.T) { 538 t.Parallel() 539 httpTest(t, nil, func(s *TestAgent) { 540 // Create the job 541 job := mock.Job() 542 args := structs.JobRegisterRequest{ 543 Job: job, 544 WriteRequest: structs.WriteRequest{ 545 Region: "global", 546 Namespace: structs.DefaultNamespace, 547 }, 548 } 549 var resp structs.JobRegisterResponse 550 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 551 t.Fatalf("err: %v", err) 552 } 553 554 // Make the HTTP request 555 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil) 556 if err != nil { 557 t.Fatalf("err: %v", err) 558 } 559 respW := httptest.NewRecorder() 560 561 // Make the request 562 obj, err := s.Server.JobSpecificRequest(respW, req) 563 if err != nil { 564 t.Fatalf("err: %v", err) 565 } 566 567 // Check the response 568 reg := obj.(structs.JobRegisterResponse) 569 if reg.EvalID == "" { 570 t.Fatalf("bad: %v", reg) 571 } 572 573 // Check for the index 574 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 575 t.Fatalf("missing index") 576 } 577 }) 578 } 579 580 func TestHTTP_JobEvaluations(t *testing.T) { 581 t.Parallel() 582 httpTest(t, nil, func(s *TestAgent) { 583 // Create the job 584 job := mock.Job() 585 args := structs.JobRegisterRequest{ 586 Job: job, 587 WriteRequest: structs.WriteRequest{ 588 Region: "global", 589 Namespace: structs.DefaultNamespace, 590 }, 591 } 592 var resp structs.JobRegisterResponse 593 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 594 t.Fatalf("err: %v", err) 595 } 596 597 // Make the HTTP request 598 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil) 599 if err != nil { 600 t.Fatalf("err: %v", err) 601 } 602 respW := httptest.NewRecorder() 603 604 // Make the request 605 obj, err := s.Server.JobSpecificRequest(respW, req) 606 if err != nil { 607 t.Fatalf("err: %v", err) 608 } 609 610 // Check the response 611 evals := obj.([]*structs.Evaluation) 612 // Can be multiple evals, use the last one, since they are in order 613 idx := len(evals) - 1 614 if len(evals) < 0 || evals[idx].ID != resp.EvalID { 615 t.Fatalf("bad: %v", evals) 616 } 617 618 // Check for the index 619 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 620 t.Fatalf("missing index") 621 } 622 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 623 t.Fatalf("missing known leader") 624 } 625 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 626 t.Fatalf("missing last contact") 627 } 628 }) 629 } 630 631 func TestHTTP_JobAllocations(t *testing.T) { 632 t.Parallel() 633 httpTest(t, nil, func(s *TestAgent) { 634 // Create the job 635 alloc1 := mock.Alloc() 636 args := structs.JobRegisterRequest{ 637 Job: alloc1.Job, 638 WriteRequest: structs.WriteRequest{ 639 Region: "global", 640 Namespace: structs.DefaultNamespace, 641 }, 642 } 643 var resp structs.JobRegisterResponse 644 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 645 t.Fatalf("err: %v", err) 646 } 647 648 // Directly manipulate the state 649 expectedDisplayMsg := "test message" 650 testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg) 651 var events []*structs.TaskEvent 652 events = append(events, testEvent) 653 taskState := &structs.TaskState{Events: events} 654 alloc1.TaskStates = make(map[string]*structs.TaskState) 655 alloc1.TaskStates["test"] = taskState 656 state := s.Agent.server.State() 657 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 658 if err != nil { 659 t.Fatalf("err: %v", err) 660 } 661 662 // Make the HTTP request 663 req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil) 664 if err != nil { 665 t.Fatalf("err: %v", err) 666 } 667 respW := httptest.NewRecorder() 668 669 // Make the request 670 obj, err := s.Server.JobSpecificRequest(respW, req) 671 if err != nil { 672 t.Fatalf("err: %v", err) 673 } 674 675 // Check the response 676 allocs := obj.([]*structs.AllocListStub) 677 if len(allocs) != 1 && allocs[0].ID != alloc1.ID { 678 t.Fatalf("bad: %v", allocs) 679 } 680 displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage 681 assert.Equal(t, expectedDisplayMsg, displayMsg) 682 683 // Check for the index 684 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 685 t.Fatalf("missing index") 686 } 687 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 688 t.Fatalf("missing known leader") 689 } 690 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 691 t.Fatalf("missing last contact") 692 } 693 }) 694 } 695 696 func TestHTTP_JobDeployments(t *testing.T) { 697 assert := assert.New(t) 698 t.Parallel() 699 httpTest(t, nil, func(s *TestAgent) { 700 // Create the job 701 j := mock.Job() 702 args := structs.JobRegisterRequest{ 703 Job: j, 704 WriteRequest: structs.WriteRequest{ 705 Region: "global", 706 Namespace: structs.DefaultNamespace, 707 }, 708 } 709 var resp structs.JobRegisterResponse 710 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 711 712 // Directly manipulate the state 713 state := s.Agent.server.State() 714 d := mock.Deployment() 715 d.JobID = j.ID 716 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 717 718 // Make the HTTP request 719 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil) 720 assert.Nil(err, "HTTP") 721 respW := httptest.NewRecorder() 722 723 // Make the request 724 obj, err := s.Server.JobSpecificRequest(respW, req) 725 assert.Nil(err, "JobSpecificRequest") 726 727 // Check the response 728 deploys := obj.([]*structs.Deployment) 729 assert.Len(deploys, 1, "deployments") 730 assert.Equal(d.ID, deploys[0].ID, "deployment id") 731 732 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 733 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 734 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 735 }) 736 } 737 738 func TestHTTP_JobDeployment(t *testing.T) { 739 assert := assert.New(t) 740 t.Parallel() 741 httpTest(t, nil, func(s *TestAgent) { 742 // Create the job 743 j := mock.Job() 744 args := structs.JobRegisterRequest{ 745 Job: j, 746 WriteRequest: structs.WriteRequest{ 747 Region: "global", 748 Namespace: structs.DefaultNamespace, 749 }, 750 } 751 var resp structs.JobRegisterResponse 752 assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") 753 754 // Directly manipulate the state 755 state := s.Agent.server.State() 756 d := mock.Deployment() 757 d.JobID = j.ID 758 assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") 759 760 // Make the HTTP request 761 req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil) 762 assert.Nil(err, "HTTP") 763 respW := httptest.NewRecorder() 764 765 // Make the request 766 obj, err := s.Server.JobSpecificRequest(respW, req) 767 assert.Nil(err, "JobSpecificRequest") 768 769 // Check the response 770 out := obj.(*structs.Deployment) 771 assert.NotNil(out, "deployment") 772 assert.Equal(d.ID, out.ID, "deployment id") 773 774 assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index") 775 assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader") 776 assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact") 777 }) 778 } 779 780 func TestHTTP_JobVersions(t *testing.T) { 781 t.Parallel() 782 httpTest(t, nil, func(s *TestAgent) { 783 // Create the job 784 job := mock.Job() 785 args := structs.JobRegisterRequest{ 786 Job: job, 787 WriteRequest: structs.WriteRequest{ 788 Region: "global", 789 Namespace: structs.DefaultNamespace, 790 }, 791 } 792 var resp structs.JobRegisterResponse 793 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 794 t.Fatalf("err: %v", err) 795 } 796 797 job2 := mock.Job() 798 job2.ID = job.ID 799 job2.Priority = 100 800 801 args2 := structs.JobRegisterRequest{ 802 Job: job2, 803 WriteRequest: structs.WriteRequest{ 804 Region: "global", 805 Namespace: structs.DefaultNamespace, 806 }, 807 } 808 var resp2 structs.JobRegisterResponse 809 if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil { 810 t.Fatalf("err: %v", err) 811 } 812 813 // Make the HTTP request 814 req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil) 815 if err != nil { 816 t.Fatalf("err: %v", err) 817 } 818 respW := httptest.NewRecorder() 819 820 // Make the request 821 obj, err := s.Server.JobSpecificRequest(respW, req) 822 if err != nil { 823 t.Fatalf("err: %v", err) 824 } 825 826 // Check the response 827 vResp := obj.(structs.JobVersionsResponse) 828 versions := vResp.Versions 829 if len(versions) != 2 { 830 t.Fatalf("got %d versions; want 2", len(versions)) 831 } 832 833 if v := versions[0]; v.Version != 1 || v.Priority != 100 { 834 t.Fatalf("bad %v", v) 835 } 836 837 if v := versions[1]; v.Version != 0 { 838 t.Fatalf("bad %v", v) 839 } 840 841 if len(vResp.Diffs) != 1 { 842 t.Fatalf("bad %v", vResp) 843 } 844 845 // Check for the index 846 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 847 t.Fatalf("missing index") 848 } 849 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 850 t.Fatalf("missing known leader") 851 } 852 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 853 t.Fatalf("missing last contact") 854 } 855 }) 856 } 857 858 func TestHTTP_PeriodicForce(t *testing.T) { 859 t.Parallel() 860 httpTest(t, nil, func(s *TestAgent) { 861 // Create and register a periodic job. 862 job := mock.PeriodicJob() 863 args := structs.JobRegisterRequest{ 864 Job: job, 865 WriteRequest: structs.WriteRequest{ 866 Region: "global", 867 Namespace: structs.DefaultNamespace, 868 }, 869 } 870 var resp structs.JobRegisterResponse 871 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 872 t.Fatalf("err: %v", err) 873 } 874 875 // Make the HTTP request 876 req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) 877 if err != nil { 878 t.Fatalf("err: %v", err) 879 } 880 respW := httptest.NewRecorder() 881 882 // Make the request 883 obj, err := s.Server.JobSpecificRequest(respW, req) 884 if err != nil { 885 t.Fatalf("err: %v", err) 886 } 887 888 // Check for the index 889 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 890 t.Fatalf("missing index") 891 } 892 893 // Check the response 894 r := obj.(structs.PeriodicForceResponse) 895 if r.EvalID == "" { 896 t.Fatalf("bad: %#v", r) 897 } 898 }) 899 } 900 901 func TestHTTP_JobPlan(t *testing.T) { 902 t.Parallel() 903 httpTest(t, nil, func(s *TestAgent) { 904 // Create the job 905 job := api.MockJob() 906 args := api.JobPlanRequest{ 907 Job: job, 908 Diff: true, 909 WriteRequest: api.WriteRequest{ 910 Region: "global", 911 Namespace: api.DefaultNamespace, 912 }, 913 } 914 buf := encodeReq(args) 915 916 // Make the HTTP request 917 req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) 918 if err != nil { 919 t.Fatalf("err: %v", err) 920 } 921 respW := httptest.NewRecorder() 922 923 // Make the request 924 obj, err := s.Server.JobSpecificRequest(respW, req) 925 if err != nil { 926 t.Fatalf("err: %v", err) 927 } 928 929 // Check the response 930 plan := obj.(structs.JobPlanResponse) 931 if plan.Annotations == nil { 932 t.Fatalf("bad: %v", plan) 933 } 934 935 if plan.Diff == nil { 936 t.Fatalf("bad: %v", plan) 937 } 938 }) 939 } 940 941 func TestHTTP_JobDispatch(t *testing.T) { 942 t.Parallel() 943 httpTest(t, nil, func(s *TestAgent) { 944 // Create the parameterized job 945 job := mock.Job() 946 job.Type = "batch" 947 job.ParameterizedJob = &structs.ParameterizedJobConfig{} 948 949 args := structs.JobRegisterRequest{ 950 Job: job, 951 WriteRequest: structs.WriteRequest{ 952 Region: "global", 953 Namespace: structs.DefaultNamespace, 954 }, 955 } 956 var resp structs.JobRegisterResponse 957 if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { 958 t.Fatalf("err: %v", err) 959 } 960 961 // Make the request 962 respW := httptest.NewRecorder() 963 args2 := structs.JobDispatchRequest{ 964 WriteRequest: structs.WriteRequest{ 965 Region: "global", 966 Namespace: structs.DefaultNamespace, 967 }, 968 } 969 buf := encodeReq(args2) 970 971 // Make the HTTP request 972 req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) 973 if err != nil { 974 t.Fatalf("err: %v", err) 975 } 976 respW.Flush() 977 978 // Make the request 979 obj, err := s.Server.JobSpecificRequest(respW, req2) 980 if err != nil { 981 t.Fatalf("err: %v", err) 982 } 983 984 // Check the response 985 dispatch := obj.(structs.JobDispatchResponse) 986 if dispatch.EvalID == "" { 987 t.Fatalf("bad: %v", dispatch) 988 } 989 990 if dispatch.DispatchedJobID == "" { 991 t.Fatalf("bad: %v", dispatch) 992 } 993 }) 994 } 995 996 func TestHTTP_JobRevert(t *testing.T) { 997 t.Parallel() 998 httpTest(t, nil, func(s *TestAgent) { 999 // Create the job and register it twice 1000 job := mock.Job() 1001 regReq := structs.JobRegisterRequest{ 1002 Job: job, 1003 WriteRequest: structs.WriteRequest{ 1004 Region: "global", 1005 Namespace: structs.DefaultNamespace, 1006 }, 1007 } 1008 var regResp structs.JobRegisterResponse 1009 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1010 t.Fatalf("err: %v", err) 1011 } 1012 1013 // Change the job to get a new version 1014 job.Datacenters = append(job.Datacenters, "foo") 1015 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1016 t.Fatalf("err: %v", err) 1017 } 1018 1019 args := structs.JobRevertRequest{ 1020 JobID: job.ID, 1021 JobVersion: 0, 1022 WriteRequest: structs.WriteRequest{ 1023 Region: "global", 1024 Namespace: structs.DefaultNamespace, 1025 }, 1026 } 1027 buf := encodeReq(args) 1028 1029 // Make the HTTP request 1030 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) 1031 if err != nil { 1032 t.Fatalf("err: %v", err) 1033 } 1034 respW := httptest.NewRecorder() 1035 1036 // Make the request 1037 obj, err := s.Server.JobSpecificRequest(respW, req) 1038 if err != nil { 1039 t.Fatalf("err: %v", err) 1040 } 1041 1042 // Check the response 1043 revertResp := obj.(structs.JobRegisterResponse) 1044 if revertResp.EvalID == "" { 1045 t.Fatalf("bad: %v", revertResp) 1046 } 1047 1048 // Check for the index 1049 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1050 t.Fatalf("missing index") 1051 } 1052 }) 1053 } 1054 1055 func TestHTTP_JobStable(t *testing.T) { 1056 t.Parallel() 1057 httpTest(t, nil, func(s *TestAgent) { 1058 // Create the job and register it twice 1059 job := mock.Job() 1060 regReq := structs.JobRegisterRequest{ 1061 Job: job, 1062 WriteRequest: structs.WriteRequest{ 1063 Region: "global", 1064 Namespace: structs.DefaultNamespace, 1065 }, 1066 } 1067 var regResp structs.JobRegisterResponse 1068 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1069 t.Fatalf("err: %v", err) 1070 } 1071 1072 if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { 1073 t.Fatalf("err: %v", err) 1074 } 1075 1076 args := structs.JobStabilityRequest{ 1077 JobID: job.ID, 1078 JobVersion: 0, 1079 Stable: true, 1080 WriteRequest: structs.WriteRequest{ 1081 Region: "global", 1082 Namespace: structs.DefaultNamespace, 1083 }, 1084 } 1085 buf := encodeReq(args) 1086 1087 // Make the HTTP request 1088 req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf) 1089 if err != nil { 1090 t.Fatalf("err: %v", err) 1091 } 1092 respW := httptest.NewRecorder() 1093 1094 // Make the request 1095 obj, err := s.Server.JobSpecificRequest(respW, req) 1096 if err != nil { 1097 t.Fatalf("err: %v", err) 1098 } 1099 1100 // Check the response 1101 stableResp := obj.(structs.JobStabilityResponse) 1102 if stableResp.Index == 0 { 1103 t.Fatalf("bad: %v", stableResp) 1104 } 1105 1106 // Check for the index 1107 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 1108 t.Fatalf("missing index") 1109 } 1110 }) 1111 } 1112 1113 func TestJobs_ApiJobToStructsJob(t *testing.T) { 1114 apiJob := &api.Job{ 1115 Stop: helper.BoolToPtr(true), 1116 Region: helper.StringToPtr("global"), 1117 Namespace: helper.StringToPtr("foo"), 1118 ID: helper.StringToPtr("foo"), 1119 ParentID: helper.StringToPtr("lol"), 1120 Name: helper.StringToPtr("name"), 1121 Type: helper.StringToPtr("service"), 1122 Priority: helper.IntToPtr(50), 1123 AllAtOnce: helper.BoolToPtr(true), 1124 Datacenters: []string{"dc1", "dc2"}, 1125 Constraints: []*api.Constraint{ 1126 { 1127 LTarget: "a", 1128 RTarget: "b", 1129 Operand: "c", 1130 }, 1131 }, 1132 Update: &api.UpdateStrategy{ 1133 Stagger: helper.TimeToPtr(1 * time.Second), 1134 MaxParallel: helper.IntToPtr(5), 1135 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), 1136 MinHealthyTime: helper.TimeToPtr(1 * time.Minute), 1137 HealthyDeadline: helper.TimeToPtr(3 * time.Minute), 1138 AutoRevert: helper.BoolToPtr(false), 1139 Canary: helper.IntToPtr(1), 1140 }, 1141 Periodic: &api.PeriodicConfig{ 1142 Enabled: helper.BoolToPtr(true), 1143 Spec: helper.StringToPtr("spec"), 1144 SpecType: helper.StringToPtr("cron"), 1145 ProhibitOverlap: helper.BoolToPtr(true), 1146 TimeZone: helper.StringToPtr("test zone"), 1147 }, 1148 ParameterizedJob: &api.ParameterizedJobConfig{ 1149 Payload: "payload", 1150 MetaRequired: []string{"a", "b"}, 1151 MetaOptional: []string{"c", "d"}, 1152 }, 1153 Payload: []byte("payload"), 1154 Meta: map[string]string{ 1155 "foo": "bar", 1156 }, 1157 TaskGroups: []*api.TaskGroup{ 1158 { 1159 Name: helper.StringToPtr("group1"), 1160 Count: helper.IntToPtr(5), 1161 Constraints: []*api.Constraint{ 1162 { 1163 LTarget: "x", 1164 RTarget: "y", 1165 Operand: "z", 1166 }, 1167 }, 1168 RestartPolicy: &api.RestartPolicy{ 1169 Interval: helper.TimeToPtr(1 * time.Second), 1170 Attempts: helper.IntToPtr(5), 1171 Delay: helper.TimeToPtr(10 * time.Second), 1172 Mode: helper.StringToPtr("delay"), 1173 }, 1174 EphemeralDisk: &api.EphemeralDisk{ 1175 SizeMB: helper.IntToPtr(100), 1176 Sticky: helper.BoolToPtr(true), 1177 Migrate: helper.BoolToPtr(true), 1178 }, 1179 Update: &api.UpdateStrategy{ 1180 HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), 1181 MinHealthyTime: helper.TimeToPtr(2 * time.Minute), 1182 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 1183 AutoRevert: helper.BoolToPtr(true), 1184 }, 1185 1186 Meta: map[string]string{ 1187 "key": "value", 1188 }, 1189 Tasks: []*api.Task{ 1190 { 1191 Name: "task1", 1192 Leader: true, 1193 Driver: "docker", 1194 User: "mary", 1195 Config: map[string]interface{}{ 1196 "lol": "code", 1197 }, 1198 Env: map[string]string{ 1199 "hello": "world", 1200 }, 1201 Constraints: []*api.Constraint{ 1202 { 1203 LTarget: "x", 1204 RTarget: "y", 1205 Operand: "z", 1206 }, 1207 }, 1208 1209 Services: []*api.Service{ 1210 { 1211 Id: "id", 1212 Name: "serviceA", 1213 Tags: []string{"1", "2"}, 1214 PortLabel: "foo", 1215 Checks: []api.ServiceCheck{ 1216 { 1217 Id: "hello", 1218 Name: "bar", 1219 Type: "http", 1220 Command: "foo", 1221 Args: []string{"a", "b"}, 1222 Path: "/check", 1223 Protocol: "http", 1224 PortLabel: "foo", 1225 AddressMode: "driver", 1226 Interval: 4 * time.Second, 1227 Timeout: 2 * time.Second, 1228 InitialStatus: "ok", 1229 CheckRestart: &api.CheckRestart{ 1230 Limit: 3, 1231 Grace: helper.TimeToPtr(10 * time.Second), 1232 IgnoreWarnings: true, 1233 }, 1234 }, 1235 }, 1236 }, 1237 }, 1238 Resources: &api.Resources{ 1239 CPU: helper.IntToPtr(100), 1240 MemoryMB: helper.IntToPtr(10), 1241 Networks: []*api.NetworkResource{ 1242 { 1243 IP: "10.10.11.1", 1244 MBits: helper.IntToPtr(10), 1245 ReservedPorts: []api.Port{ 1246 { 1247 Label: "http", 1248 Value: 80, 1249 }, 1250 }, 1251 DynamicPorts: []api.Port{ 1252 { 1253 Label: "ssh", 1254 Value: 2000, 1255 }, 1256 }, 1257 }, 1258 }, 1259 }, 1260 Meta: map[string]string{ 1261 "lol": "code", 1262 }, 1263 KillTimeout: helper.TimeToPtr(10 * time.Second), 1264 KillSignal: "SIGQUIT", 1265 LogConfig: &api.LogConfig{ 1266 MaxFiles: helper.IntToPtr(10), 1267 MaxFileSizeMB: helper.IntToPtr(100), 1268 }, 1269 Artifacts: []*api.TaskArtifact{ 1270 { 1271 GetterSource: helper.StringToPtr("source"), 1272 GetterOptions: map[string]string{ 1273 "a": "b", 1274 }, 1275 GetterMode: helper.StringToPtr("dir"), 1276 RelativeDest: helper.StringToPtr("dest"), 1277 }, 1278 }, 1279 Vault: &api.Vault{ 1280 Policies: []string{"a", "b", "c"}, 1281 Env: helper.BoolToPtr(true), 1282 ChangeMode: helper.StringToPtr("c"), 1283 ChangeSignal: helper.StringToPtr("sighup"), 1284 }, 1285 Templates: []*api.Template{ 1286 { 1287 SourcePath: helper.StringToPtr("source"), 1288 DestPath: helper.StringToPtr("dest"), 1289 EmbeddedTmpl: helper.StringToPtr("embedded"), 1290 ChangeMode: helper.StringToPtr("change"), 1291 ChangeSignal: helper.StringToPtr("signal"), 1292 Splay: helper.TimeToPtr(1 * time.Minute), 1293 Perms: helper.StringToPtr("666"), 1294 LeftDelim: helper.StringToPtr("abc"), 1295 RightDelim: helper.StringToPtr("def"), 1296 Envvars: helper.BoolToPtr(true), 1297 VaultGrace: helper.TimeToPtr(3 * time.Second), 1298 }, 1299 }, 1300 DispatchPayload: &api.DispatchPayloadConfig{ 1301 File: "fileA", 1302 }, 1303 }, 1304 }, 1305 }, 1306 }, 1307 VaultToken: helper.StringToPtr("token"), 1308 Status: helper.StringToPtr("status"), 1309 StatusDescription: helper.StringToPtr("status_desc"), 1310 Version: helper.Uint64ToPtr(10), 1311 CreateIndex: helper.Uint64ToPtr(1), 1312 ModifyIndex: helper.Uint64ToPtr(3), 1313 JobModifyIndex: helper.Uint64ToPtr(5), 1314 } 1315 1316 expected := &structs.Job{ 1317 Stop: true, 1318 Region: "global", 1319 Namespace: "foo", 1320 ID: "foo", 1321 ParentID: "lol", 1322 Name: "name", 1323 Type: "service", 1324 Priority: 50, 1325 AllAtOnce: true, 1326 Datacenters: []string{"dc1", "dc2"}, 1327 Constraints: []*structs.Constraint{ 1328 { 1329 LTarget: "a", 1330 RTarget: "b", 1331 Operand: "c", 1332 }, 1333 }, 1334 Update: structs.UpdateStrategy{ 1335 Stagger: 1 * time.Second, 1336 MaxParallel: 5, 1337 }, 1338 Periodic: &structs.PeriodicConfig{ 1339 Enabled: true, 1340 Spec: "spec", 1341 SpecType: "cron", 1342 ProhibitOverlap: true, 1343 TimeZone: "test zone", 1344 }, 1345 ParameterizedJob: &structs.ParameterizedJobConfig{ 1346 Payload: "payload", 1347 MetaRequired: []string{"a", "b"}, 1348 MetaOptional: []string{"c", "d"}, 1349 }, 1350 Payload: []byte("payload"), 1351 Meta: map[string]string{ 1352 "foo": "bar", 1353 }, 1354 TaskGroups: []*structs.TaskGroup{ 1355 { 1356 Name: "group1", 1357 Count: 5, 1358 Constraints: []*structs.Constraint{ 1359 { 1360 LTarget: "x", 1361 RTarget: "y", 1362 Operand: "z", 1363 }, 1364 }, 1365 RestartPolicy: &structs.RestartPolicy{ 1366 Interval: 1 * time.Second, 1367 Attempts: 5, 1368 Delay: 10 * time.Second, 1369 Mode: "delay", 1370 }, 1371 EphemeralDisk: &structs.EphemeralDisk{ 1372 SizeMB: 100, 1373 Sticky: true, 1374 Migrate: true, 1375 }, 1376 Update: &structs.UpdateStrategy{ 1377 Stagger: 1 * time.Second, 1378 MaxParallel: 5, 1379 HealthCheck: structs.UpdateStrategyHealthCheck_Checks, 1380 MinHealthyTime: 2 * time.Minute, 1381 HealthyDeadline: 5 * time.Minute, 1382 AutoRevert: true, 1383 Canary: 1, 1384 }, 1385 Meta: map[string]string{ 1386 "key": "value", 1387 }, 1388 Tasks: []*structs.Task{ 1389 { 1390 Name: "task1", 1391 Driver: "docker", 1392 Leader: true, 1393 User: "mary", 1394 Config: map[string]interface{}{ 1395 "lol": "code", 1396 }, 1397 Constraints: []*structs.Constraint{ 1398 { 1399 LTarget: "x", 1400 RTarget: "y", 1401 Operand: "z", 1402 }, 1403 }, 1404 Env: map[string]string{ 1405 "hello": "world", 1406 }, 1407 Services: []*structs.Service{ 1408 { 1409 Name: "serviceA", 1410 Tags: []string{"1", "2"}, 1411 PortLabel: "foo", 1412 AddressMode: "auto", 1413 Checks: []*structs.ServiceCheck{ 1414 { 1415 Name: "bar", 1416 Type: "http", 1417 Command: "foo", 1418 Args: []string{"a", "b"}, 1419 Path: "/check", 1420 Protocol: "http", 1421 PortLabel: "foo", 1422 AddressMode: "driver", 1423 Interval: 4 * time.Second, 1424 Timeout: 2 * time.Second, 1425 InitialStatus: "ok", 1426 CheckRestart: &structs.CheckRestart{ 1427 Limit: 3, 1428 Grace: 10 * time.Second, 1429 IgnoreWarnings: true, 1430 }, 1431 }, 1432 }, 1433 }, 1434 }, 1435 Resources: &structs.Resources{ 1436 CPU: 100, 1437 MemoryMB: 10, 1438 Networks: []*structs.NetworkResource{ 1439 { 1440 IP: "10.10.11.1", 1441 MBits: 10, 1442 ReservedPorts: []structs.Port{ 1443 { 1444 Label: "http", 1445 Value: 80, 1446 }, 1447 }, 1448 DynamicPorts: []structs.Port{ 1449 { 1450 Label: "ssh", 1451 Value: 2000, 1452 }, 1453 }, 1454 }, 1455 }, 1456 }, 1457 Meta: map[string]string{ 1458 "lol": "code", 1459 }, 1460 KillTimeout: 10 * time.Second, 1461 KillSignal: "SIGQUIT", 1462 LogConfig: &structs.LogConfig{ 1463 MaxFiles: 10, 1464 MaxFileSizeMB: 100, 1465 }, 1466 Artifacts: []*structs.TaskArtifact{ 1467 { 1468 GetterSource: "source", 1469 GetterOptions: map[string]string{ 1470 "a": "b", 1471 }, 1472 GetterMode: "dir", 1473 RelativeDest: "dest", 1474 }, 1475 }, 1476 Vault: &structs.Vault{ 1477 Policies: []string{"a", "b", "c"}, 1478 Env: true, 1479 ChangeMode: "c", 1480 ChangeSignal: "sighup", 1481 }, 1482 Templates: []*structs.Template{ 1483 { 1484 SourcePath: "source", 1485 DestPath: "dest", 1486 EmbeddedTmpl: "embedded", 1487 ChangeMode: "change", 1488 ChangeSignal: "SIGNAL", 1489 Splay: 1 * time.Minute, 1490 Perms: "666", 1491 LeftDelim: "abc", 1492 RightDelim: "def", 1493 Envvars: true, 1494 VaultGrace: 3 * time.Second, 1495 }, 1496 }, 1497 DispatchPayload: &structs.DispatchPayloadConfig{ 1498 File: "fileA", 1499 }, 1500 }, 1501 }, 1502 }, 1503 }, 1504 1505 VaultToken: "token", 1506 } 1507 1508 structsJob := ApiJobToStructJob(apiJob) 1509 1510 if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { 1511 t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) 1512 } 1513 }