package agent import ( "net/http" "net/http/httptest" "reflect" "testing" "time" "github.com/golang/snappy" "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" ) func TestHTTP_JobsList(t *testing.T) { httpTest(t, nil, func(s *TestServer) { for i := 0; i < 3; i++ { // Create the job job := mock.Job() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/jobs", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobsRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } // Check the job j := obj.([]*structs.JobListStub) if len(j) != 3 { t.Fatalf("bad: %#v", j) } }) } func TestHTTP_PrefixJobsList(t *testing.T) { ids := []string{ "aaaaaaaa-e8f7-fd38-c855-ab94ceb89706", "aabbbbbb-e8f7-fd38-c855-ab94ceb89706", "aabbcccc-e8f7-fd38-c855-ab94ceb89706", } httpTest(t, nil, func(s *TestServer) { for i := 0; i < 3; i++ { // Create the job job := mock.Job() job.ID = ids[i] job.TaskGroups[0].Count = 1 args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobsRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } // Check the job j := obj.([]*structs.JobListStub) if len(j) != 2 { t.Fatalf("bad: %#v", j) } }) } func TestHTTP_JobsRegister(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := api.MockJob() args := api.JobRegisterRequest{ Job: job, WriteRequest: api.WriteRequest{Region: "global"}, } buf := encodeReq(args) // Make the HTTP request req, err := http.NewRequest("PUT", "/v1/jobs", buf) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobsRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response dereg := obj.(structs.JobRegisterResponse) if dereg.EvalID == "" { t.Fatalf("bad: %v", dereg) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the job is registered getReq := structs.JobSpecificRequest{ JobID: *job.ID, QueryOptions: structs.QueryOptions{Region: "global"}, } var getResp structs.SingleJobResponse if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { t.Fatalf("err: %v", err) } if getResp.Job == nil { t.Fatalf("job does not exist") } }) } func TestHTTP_JobsRegister_Defaulting(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := api.MockJob() // Do not set its priority job.Priority = nil args := api.JobRegisterRequest{ Job: job, WriteRequest: api.WriteRequest{Region: "global"}, } buf := encodeReq(args) // Make the HTTP request req, err := http.NewRequest("PUT", "/v1/jobs", buf) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobsRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response dereg := obj.(structs.JobRegisterResponse) if dereg.EvalID == "" { t.Fatalf("bad: %v", dereg) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the job is registered getReq := structs.JobSpecificRequest{ JobID: *job.ID, QueryOptions: structs.QueryOptions{Region: "global"}, } var getResp structs.SingleJobResponse if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { t.Fatalf("err: %v", err) } if getResp.Job == nil { t.Fatalf("job does not exist") } if getResp.Job.Priority != 50 { t.Fatalf("job didn't get defaulted") } }) } func TestHTTP_JobQuery(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } // Check the job j := obj.(*structs.Job) if j.ID != job.ID { t.Fatalf("bad: %#v", j) } }) } func TestHTTP_JobQuery_Payload(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() // Insert Payload compressed expected := []byte("hello world") compressed := snappy.Encode(nil, expected) job.Payload = compressed // Directly manipulate the state state := s.Agent.server.State() if err := state.UpsertJob(1000, job); err != nil { t.Fatalf("Failed to upsert job: %v", err) } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } // Check the job j := obj.(*structs.Job) if j.ID != job.ID { t.Fatalf("bad: %#v", j) } // Check the payload is decompressed if !reflect.DeepEqual(j.Payload, expected) { t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected) } }) } func TestHTTP_JobUpdate(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := api.MockJob() args := api.JobRegisterRequest{ Job: job, WriteRequest: api.WriteRequest{Region: "global"}, } buf := encodeReq(args) // Make the HTTP request req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response dereg := obj.(structs.JobRegisterResponse) if dereg.EvalID == "" { t.Fatalf("bad: %v", dereg) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the job is registered getReq := structs.JobSpecificRequest{ JobID: *job.ID, QueryOptions: structs.QueryOptions{Region: "global"}, } var getResp structs.SingleJobResponse if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil { t.Fatalf("err: %v", err) } if getResp.Job == nil { t.Fatalf("job does not exist") } }) } func TestHTTP_JobDelete(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request to do a soft delete req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response dereg := obj.(structs.JobDeregisterResponse) if dereg.EvalID == "" { t.Fatalf("bad: %v", dereg) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the job is still queryable getReq1 := structs.JobSpecificRequest{ JobID: job.ID, QueryOptions: structs.QueryOptions{Region: "global"}, } var getResp1 structs.SingleJobResponse if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil { t.Fatalf("err: %v", err) } if getResp1.Job == nil { t.Fatalf("job doesn't exists") } if !getResp1.Job.Stop { t.Fatalf("job should be marked as stop") } // Make the HTTP request to do a purge delete req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil) if err != nil { t.Fatalf("err: %v", err) } respW.Flush() // Make the request obj, err = s.Server.JobSpecificRequest(respW, req2) if err != nil { t.Fatalf("err: %v", err) } // Check the response dereg = obj.(structs.JobDeregisterResponse) if dereg.EvalID == "" { t.Fatalf("bad: %v", dereg) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the job is gone getReq2 := structs.JobSpecificRequest{ JobID: job.ID, QueryOptions: structs.QueryOptions{Region: "global"}, } var getResp2 structs.SingleJobResponse if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil { t.Fatalf("err: %v", err) } if getResp2.Job != nil { t.Fatalf("job still exists") } }) } func TestHTTP_JobForceEvaluate(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response reg := obj.(structs.JobRegisterResponse) if reg.EvalID == "" { t.Fatalf("bad: %v", reg) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } }) } func TestHTTP_JobEvaluations(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response evals := obj.([]*structs.Evaluation) // Can be multiple evals, use the last one, since they are in order idx := len(evals) - 1 if len(evals) < 0 || evals[idx].ID != resp.EvalID { t.Fatalf("bad: %v", evals) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } }) } func TestHTTP_JobAllocations(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job alloc1 := mock.Alloc() args := structs.JobRegisterRequest{ Job: alloc1.Job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Directly manipulate the state state := s.Agent.server.State() err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) if err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response allocs := obj.([]*structs.AllocListStub) if len(allocs) != 1 && allocs[0].ID != alloc1.ID { t.Fatalf("bad: %v", allocs) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } }) } func TestHTTP_JobVersions(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } job2 := mock.Job() job2.ID = job.ID job2.Priority = 100 args2 := structs.JobRegisterRequest{ Job: job2, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp2 structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response versions := obj.([]*structs.Job) if len(versions) != 2 { t.Fatalf("got %d versions; want 2", len(versions)) } if v := versions[0]; v.Version != 1 || v.Priority != 100 { t.Fatalf("bad %v", v) } if v := versions[1]; v.Version != 0 { t.Fatalf("bad %v", v) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } }) } func TestHTTP_PeriodicForce(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create and register a periodic job. job := mock.PeriodicJob() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the HTTP request req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } // Check the response r := obj.(structs.PeriodicForceResponse) if r.EvalID == "" { t.Fatalf("bad: %#v", r) } }) } func TestHTTP_JobPlan(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job job := mock.Job() args := structs.JobPlanRequest{ Job: job, Diff: true, WriteRequest: structs.WriteRequest{Region: "global"}, } buf := encodeReq(args) // Make the HTTP request req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/plan", buf) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response plan := obj.(structs.JobPlanResponse) if plan.Annotations == nil { t.Fatalf("bad: %v", plan) } if plan.Diff == nil { t.Fatalf("bad: %v", plan) } }) } func TestHTTP_JobDispatch(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the parameterized job job := mock.Job() job.Type = "batch" job.ParameterizedJob = &structs.ParameterizedJobConfig{} args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var resp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { t.Fatalf("err: %v", err) } // Make the request respW := httptest.NewRecorder() args2 := structs.JobDispatchRequest{ WriteRequest: structs.WriteRequest{Region: "global"}, } buf := encodeReq(args2) // Make the HTTP request req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf) if err != nil { t.Fatalf("err: %v", err) } respW.Flush() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req2) if err != nil { t.Fatalf("err: %v", err) } // Check the response dispatch := obj.(structs.JobDispatchResponse) if dispatch.EvalID == "" { t.Fatalf("bad: %v", dispatch) } if dispatch.DispatchedJobID == "" { t.Fatalf("bad: %v", dispatch) } }) } func TestHTTP_JobRevert(t *testing.T) { httpTest(t, nil, func(s *TestServer) { // Create the job and register it twice job := mock.Job() regReq := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{Region: "global"}, } var regResp structs.JobRegisterResponse if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { t.Fatalf("err: %v", err) } if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { t.Fatalf("err: %v", err) } args := structs.JobRevertRequest{ JobID: job.ID, JobVersion: 0, WriteRequest: structs.WriteRequest{Region: "global"}, } buf := encodeReq(args) // Make the HTTP request req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check the response revertResp := obj.(structs.JobRegisterResponse) if revertResp.EvalID == "" { t.Fatalf("bad: %v", revertResp) } // Check for the index if respW.HeaderMap.Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } }) } func TestJobs_ApiJobToStructsJob(t *testing.T) { apiJob := &api.Job{ Stop: helper.BoolToPtr(true), Region: helper.StringToPtr("global"), ID: helper.StringToPtr("foo"), ParentID: helper.StringToPtr("lol"), Name: helper.StringToPtr("name"), Type: helper.StringToPtr("service"), Priority: helper.IntToPtr(50), AllAtOnce: helper.BoolToPtr(true), Datacenters: []string{"dc1", "dc2"}, Constraints: []*api.Constraint{ { LTarget: "a", RTarget: "b", Operand: "c", }, }, Update: &api.UpdateStrategy{ Stagger: 1 * time.Second, MaxParallel: 5, }, Periodic: &api.PeriodicConfig{ Enabled: helper.BoolToPtr(true), Spec: helper.StringToPtr("spec"), SpecType: helper.StringToPtr("cron"), ProhibitOverlap: helper.BoolToPtr(true), TimeZone: helper.StringToPtr("test zone"), }, ParameterizedJob: &api.ParameterizedJobConfig{ Payload: "payload", MetaRequired: []string{"a", "b"}, MetaOptional: []string{"c", "d"}, }, Payload: []byte("payload"), Meta: map[string]string{ "foo": "bar", }, TaskGroups: []*api.TaskGroup{ { Name: helper.StringToPtr("group1"), Count: helper.IntToPtr(5), Constraints: []*api.Constraint{ { LTarget: "x", RTarget: "y", Operand: "z", }, }, RestartPolicy: &api.RestartPolicy{ Interval: helper.TimeToPtr(1 * time.Second), Attempts: helper.IntToPtr(5), Delay: helper.TimeToPtr(10 * time.Second), Mode: helper.StringToPtr("delay"), }, EphemeralDisk: &api.EphemeralDisk{ SizeMB: helper.IntToPtr(100), Sticky: helper.BoolToPtr(true), Migrate: helper.BoolToPtr(true), }, Meta: map[string]string{ "key": "value", }, Tasks: []*api.Task{ { Name: "task1", Leader: true, Driver: "docker", User: "mary", Config: map[string]interface{}{ "lol": "code", }, Env: map[string]string{ "hello": "world", }, Constraints: []*api.Constraint{ { LTarget: "x", RTarget: "y", Operand: "z", }, }, Services: []*api.Service{ { Id: "id", Name: "serviceA", Tags: []string{"1", "2"}, PortLabel: "foo", Checks: []api.ServiceCheck{ { Id: "hello", Name: "bar", Type: "http", Command: "foo", Args: []string{"a", "b"}, Path: "/check", Protocol: "http", PortLabel: "foo", Interval: 4 * time.Second, Timeout: 2 * time.Second, InitialStatus: "ok", }, }, }, }, Resources: &api.Resources{ CPU: helper.IntToPtr(100), MemoryMB: helper.IntToPtr(10), Networks: []*api.NetworkResource{ { IP: "10.10.11.1", MBits: helper.IntToPtr(10), ReservedPorts: []api.Port{ { Label: "http", Value: 80, }, }, DynamicPorts: []api.Port{ { Label: "ssh", Value: 2000, }, }, }, }, }, Meta: map[string]string{ "lol": "code", }, KillTimeout: helper.TimeToPtr(10 * time.Second), LogConfig: &api.LogConfig{ MaxFiles: helper.IntToPtr(10), MaxFileSizeMB: helper.IntToPtr(100), }, Artifacts: []*api.TaskArtifact{ { GetterSource: helper.StringToPtr("source"), GetterOptions: map[string]string{ "a": "b", }, RelativeDest: helper.StringToPtr("dest"), }, }, Vault: &api.Vault{ Policies: []string{"a", "b", "c"}, Env: helper.BoolToPtr(true), ChangeMode: helper.StringToPtr("c"), ChangeSignal: helper.StringToPtr("sighup"), }, Templates: []*api.Template{ { SourcePath: helper.StringToPtr("source"), DestPath: helper.StringToPtr("dest"), EmbeddedTmpl: helper.StringToPtr("embedded"), ChangeMode: helper.StringToPtr("change"), ChangeSignal: helper.StringToPtr("signal"), Splay: helper.TimeToPtr(1 * time.Minute), Perms: helper.StringToPtr("666"), LeftDelim: helper.StringToPtr("abc"), RightDelim: helper.StringToPtr("def"), }, }, DispatchPayload: &api.DispatchPayloadConfig{ File: "fileA", }, }, }, }, }, VaultToken: helper.StringToPtr("token"), Status: helper.StringToPtr("status"), StatusDescription: helper.StringToPtr("status_desc"), Version: helper.Uint64ToPtr(10), CreateIndex: helper.Uint64ToPtr(1), ModifyIndex: helper.Uint64ToPtr(3), JobModifyIndex: helper.Uint64ToPtr(5), } expected := &structs.Job{ Stop: true, Region: "global", ID: "foo", ParentID: "lol", Name: "name", Type: "service", Priority: 50, AllAtOnce: true, Datacenters: []string{"dc1", "dc2"}, Constraints: []*structs.Constraint{ { LTarget: "a", RTarget: "b", Operand: "c", }, }, Update: structs.UpdateStrategy{ Stagger: 1 * time.Second, MaxParallel: 5, }, Periodic: &structs.PeriodicConfig{ Enabled: true, Spec: "spec", SpecType: "cron", ProhibitOverlap: true, TimeZone: "test zone", }, ParameterizedJob: &structs.ParameterizedJobConfig{ Payload: "payload", MetaRequired: []string{"a", "b"}, MetaOptional: []string{"c", "d"}, }, Payload: []byte("payload"), Meta: map[string]string{ "foo": "bar", }, TaskGroups: []*structs.TaskGroup{ { Name: "group1", Count: 5, Constraints: []*structs.Constraint{ { LTarget: "x", RTarget: "y", Operand: "z", }, }, RestartPolicy: &structs.RestartPolicy{ Interval: 1 * time.Second, Attempts: 5, Delay: 10 * time.Second, Mode: "delay", }, EphemeralDisk: &structs.EphemeralDisk{ SizeMB: 100, Sticky: true, Migrate: true, }, Meta: map[string]string{ "key": "value", }, Tasks: []*structs.Task{ { Name: "task1", Driver: "docker", Leader: true, User: "mary", Config: map[string]interface{}{ "lol": "code", }, Constraints: []*structs.Constraint{ { LTarget: "x", RTarget: "y", Operand: "z", }, }, Env: map[string]string{ "hello": "world", }, Services: []*structs.Service{ &structs.Service{ Name: "serviceA", Tags: []string{"1", "2"}, PortLabel: "foo", Checks: []*structs.ServiceCheck{ &structs.ServiceCheck{ Name: "bar", Type: "http", Command: "foo", Args: []string{"a", "b"}, Path: "/check", Protocol: "http", PortLabel: "foo", Interval: 4 * time.Second, Timeout: 2 * time.Second, InitialStatus: "ok", }, }, }, }, Resources: &structs.Resources{ CPU: 100, MemoryMB: 10, Networks: []*structs.NetworkResource{ { IP: "10.10.11.1", MBits: 10, ReservedPorts: []structs.Port{ { Label: "http", Value: 80, }, }, DynamicPorts: []structs.Port{ { Label: "ssh", Value: 2000, }, }, }, }, }, Meta: map[string]string{ "lol": "code", }, KillTimeout: 10 * time.Second, LogConfig: &structs.LogConfig{ MaxFiles: 10, MaxFileSizeMB: 100, }, Artifacts: []*structs.TaskArtifact{ { GetterSource: "source", GetterOptions: map[string]string{ "a": "b", }, RelativeDest: "dest", }, }, Vault: &structs.Vault{ Policies: []string{"a", "b", "c"}, Env: true, ChangeMode: "c", ChangeSignal: "sighup", }, Templates: []*structs.Template{ { SourcePath: "source", DestPath: "dest", EmbeddedTmpl: "embedded", ChangeMode: "change", ChangeSignal: "SIGNAL", Splay: 1 * time.Minute, Perms: "666", LeftDelim: "abc", RightDelim: "def", }, }, DispatchPayload: &structs.DispatchPayloadConfig{ File: "fileA", }, }, }, }, }, VaultToken: "token", } structsJob := ApiJobToStructJob(apiJob) if !reflect.DeepEqual(expected, structsJob) { t.Fatalf("bad %#v", structsJob) } }