package agent import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/require" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" ) func TestHTTP_ScalingPoliciesList(t *testing.T) { t.Parallel() httpTest(t, nil, func(s *TestAgent) { for i := 0; i < 3; i++ { // Create the job job, _ := mock.JobWithScalingPolicy() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{ Region: "global", Namespace: structs.DefaultNamespace, }, } 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/scaling/policies", nil) if err != nil { t.Fatalf("err: %v", err) } respW := httptest.NewRecorder() // Make the request obj, err := s.Server.ScalingPoliciesRequest(respW, req) if err != nil { t.Fatalf("err: %v", err) } // Check for the index if respW.Header().Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.Header().Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.Header().Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } // Check the list l := obj.([]*structs.ScalingPolicyListStub) if len(l) != 3 { t.Fatalf("bad: %#v", l) } }) } func TestHTTP_ScalingPolicyGet(t *testing.T) { t.Parallel() require := require.New(t) httpTest(t, nil, func(s *TestAgent) { // Create the job job, p := mock.JobWithScalingPolicy() args := structs.JobRegisterRequest{ Job: job, WriteRequest: structs.WriteRequest{ Region: "global", Namespace: structs.DefaultNamespace, }, } var resp structs.JobRegisterResponse err := s.Agent.RPC("Job.Register", &args, &resp) require.NoError(err) // Make the HTTP request req, err := http.NewRequest("GET", "/v1/scaling/policy/"+p.ID, nil) require.NoError(err) respW := httptest.NewRecorder() // Make the request obj, err := s.Server.ScalingPolicySpecificRequest(respW, req) require.NoError(err) // Check for the index if respW.Header().Get("X-Nomad-Index") == "" { t.Fatalf("missing index") } if respW.Header().Get("X-Nomad-KnownLeader") != "true" { t.Fatalf("missing known leader") } if respW.Header().Get("X-Nomad-LastContact") == "" { t.Fatalf("missing last contact") } // Check the policy require.Equal(p.ID, obj.(*structs.ScalingPolicy).ID) }) } // func TestHTTP_JobQuery_Payload(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // 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.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // if respW.Header().Get("X-Nomad-KnownLeader") != "true" { // t.Fatalf("missing known leader") // } // if respW.Header().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) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := MockJob() // args := api.JobRegisterRequest{ // Job: job, // WriteRequest: api.WriteRequest{ // Region: "global", // Namespace: api.DefaultNamespace, // }, // } // 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.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // // // Check the job is registered // getReq := structs.JobSpecificRequest{ // JobID: *job.ID, // QueryOptions: structs.QueryOptions{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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_JobUpdateRegion(t *testing.T) { // t.Parallel() // // cases := []struct { // Name string // ConfigRegion string // APIRegion string // ExpectedRegion string // }{ // { // Name: "api region takes precedence", // ConfigRegion: "not-global", // APIRegion: "north-america", // ExpectedRegion: "north-america", // }, // { // Name: "config region is set", // ConfigRegion: "north-america", // APIRegion: "", // ExpectedRegion: "north-america", // }, // { // Name: "api region is set", // ConfigRegion: "", // APIRegion: "north-america", // ExpectedRegion: "north-america", // }, // { // Name: "defaults to node region global if no region is provided", // ConfigRegion: "", // APIRegion: "", // ExpectedRegion: "global", // }, // { // Name: "defaults to node region not-global if no region is provided", // ConfigRegion: "", // APIRegion: "", // ExpectedRegion: "not-global", // }, // } // // for _, tc := range cases { // t.Run(tc.Name, func(t *testing.T) { // httpTest(t, func(c *Config) { c.Region = tc.ExpectedRegion }, func(s *TestAgent) { // // Create the job // job := MockRegionalJob() // // if tc.ConfigRegion == "" { // job.Region = nil // } else { // job.Region = &tc.ConfigRegion // } // // args := api.JobRegisterRequest{ // Job: job, // WriteRequest: api.WriteRequest{ // Namespace: api.DefaultNamespace, // Region: tc.APIRegion, // }, // } // // buf := encodeReq(args) // // // Make the HTTP request // url := "/v1/job/" + *job.ID // // req, err := http.NewRequest("PUT", url, buf) // require.NoError(t, err) // respW := httptest.NewRecorder() // // // Make the request // obj, err := s.Server.JobSpecificRequest(respW, req) // require.NoError(t, err) // // // Check the response // dereg := obj.(structs.JobRegisterResponse) // require.NotEmpty(t, dereg.EvalID) // // // Check for the index // require.NotEmpty(t, respW.Header().Get("X-Nomad-Index"), "missing index") // // // Check the job is registered // getReq := structs.JobSpecificRequest{ // JobID: *job.ID, // QueryOptions: structs.QueryOptions{ // Region: tc.ExpectedRegion, // Namespace: structs.DefaultNamespace, // }, // } // var getResp structs.SingleJobResponse // err = s.Agent.RPC("Job.GetJob", &getReq, &getResp) // require.NoError(t, err) // require.NotNil(t, getResp.Job, "job does not exist") // require.Equal(t, tc.ExpectedRegion, getResp.Job.Region) // }) // }) // } // } // // func TestHTTP_JobDelete(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := mock.Job() // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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.Header().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", // Namespace: structs.DefaultNamespace, // }, // } // 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.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // // // Check the job is gone // getReq2 := structs.JobSpecificRequest{ // JobID: job.ID, // QueryOptions: structs.QueryOptions{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := mock.Job() // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // }) // } // // func TestHTTP_JobEvaluate_ForceReschedule(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := mock.Job() // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // var resp structs.JobRegisterResponse // if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { // t.Fatalf("err: %v", err) // } // jobEvalReq := api.JobEvaluateRequest{ // JobID: job.ID, // EvalOptions: api.EvalOptions{ // ForceReschedule: true, // }, // } // // buf := encodeReq(jobEvalReq) // // // Make the HTTP request // req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", 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 // reg := obj.(structs.JobRegisterResponse) // if reg.EvalID == "" { // t.Fatalf("bad: %v", reg) // } // // // Check for the index // if respW.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // }) // } // // func TestHTTP_JobEvaluations(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := mock.Job() // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // if respW.Header().Get("X-Nomad-KnownLeader") != "true" { // t.Fatalf("missing known leader") // } // if respW.Header().Get("X-Nomad-LastContact") == "" { // t.Fatalf("missing last contact") // } // }) // } // // func TestHTTP_JobAllocations(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // alloc1 := mock.Alloc() // args := structs.JobRegisterRequest{ // Job: alloc1.Job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // var resp structs.JobRegisterResponse // if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil { // t.Fatalf("err: %v", err) // } // // // Directly manipulate the state // expectedDisplayMsg := "test message" // testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg) // var events []*structs.TaskEvent // events = append(events, testEvent) // taskState := &structs.TaskState{Events: events} // alloc1.TaskStates = make(map[string]*structs.TaskState) // alloc1.TaskStates["test"] = taskState // 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) // } // displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage // assert.Equal(t, expectedDisplayMsg, displayMsg) // // // Check for the index // if respW.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // if respW.Header().Get("X-Nomad-KnownLeader") != "true" { // t.Fatalf("missing known leader") // } // if respW.Header().Get("X-Nomad-LastContact") == "" { // t.Fatalf("missing last contact") // } // }) // } // // func TestHTTP_JobDeployments(t *testing.T) { // assert := assert.New(t) // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // j := mock.Job() // args := structs.JobRegisterRequest{ // Job: j, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // var resp structs.JobRegisterResponse // assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") // // // Directly manipulate the state // state := s.Agent.server.State() // d := mock.Deployment() // d.JobID = j.ID // d.JobCreateIndex = resp.JobModifyIndex // // assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") // // // Make the HTTP request // req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil) // assert.Nil(err, "HTTP") // respW := httptest.NewRecorder() // // // Make the request // obj, err := s.Server.JobSpecificRequest(respW, req) // assert.Nil(err, "JobSpecificRequest") // // // Check the response // deploys := obj.([]*structs.Deployment) // assert.Len(deploys, 1, "deployments") // assert.Equal(d.ID, deploys[0].ID, "deployment id") // // assert.NotZero(respW.Header().Get("X-Nomad-Index"), "missing index") // assert.Equal("true", respW.Header().Get("X-Nomad-KnownLeader"), "missing known leader") // assert.NotZero(respW.Header().Get("X-Nomad-LastContact"), "missing last contact") // }) // } // // func TestHTTP_JobDeployment(t *testing.T) { // assert := assert.New(t) // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // j := mock.Job() // args := structs.JobRegisterRequest{ // Job: j, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // var resp structs.JobRegisterResponse // assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister") // // // Directly manipulate the state // state := s.Agent.server.State() // d := mock.Deployment() // d.JobID = j.ID // d.JobCreateIndex = resp.JobModifyIndex // assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment") // // // Make the HTTP request // req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil) // assert.Nil(err, "HTTP") // respW := httptest.NewRecorder() // // // Make the request // obj, err := s.Server.JobSpecificRequest(respW, req) // assert.Nil(err, "JobSpecificRequest") // // // Check the response // out := obj.(*structs.Deployment) // assert.NotNil(out, "deployment") // assert.Equal(d.ID, out.ID, "deployment id") // // assert.NotZero(respW.Header().Get("X-Nomad-Index"), "missing index") // assert.Equal("true", respW.Header().Get("X-Nomad-KnownLeader"), "missing known leader") // assert.NotZero(respW.Header().Get("X-Nomad-LastContact"), "missing last contact") // }) // } // // func TestHTTP_JobVersions(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := mock.Job() // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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", // Namespace: structs.DefaultNamespace, // }, // } // 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?diffs=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 // vResp := obj.(structs.JobVersionsResponse) // versions := vResp.Versions // 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) // } // // if len(vResp.Diffs) != 1 { // t.Fatalf("bad %v", vResp) // } // // // Check for the index // if respW.Header().Get("X-Nomad-Index") == "" { // t.Fatalf("missing index") // } // if respW.Header().Get("X-Nomad-KnownLeader") != "true" { // t.Fatalf("missing known leader") // } // if respW.Header().Get("X-Nomad-LastContact") == "" { // t.Fatalf("missing last contact") // } // }) // } // // func TestHTTP_PeriodicForce(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create and register a periodic job. // job := mock.PeriodicJob() // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := MockJob() // args := api.JobPlanRequest{ // Job: job, // Diff: true, // WriteRequest: api.WriteRequest{ // Region: "global", // Namespace: api.DefaultNamespace, // }, // } // 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_JobPlanRegion(t *testing.T) { // t.Parallel() // // cases := []struct { // Name string // ConfigRegion string // APIRegion string // ExpectedRegion string // }{ // { // Name: "api region takes precedence", // ConfigRegion: "not-global", // APIRegion: "north-america", // ExpectedRegion: "north-america", // }, // { // Name: "config region is set", // ConfigRegion: "north-america", // APIRegion: "", // ExpectedRegion: "north-america", // }, // { // Name: "api region is set", // ConfigRegion: "", // APIRegion: "north-america", // ExpectedRegion: "north-america", // }, // { // Name: "falls back to default if no region is provided", // ConfigRegion: "", // APIRegion: "", // ExpectedRegion: "global", // }, // } // // for _, tc := range cases { // t.Run(tc.Name, func(t *testing.T) { // httpTest(t, func(c *Config) { c.Region = tc.ExpectedRegion }, func(s *TestAgent) { // // Create the job // job := MockRegionalJob() // // if tc.ConfigRegion == "" { // job.Region = nil // } else { // job.Region = &tc.ConfigRegion // } // // args := api.JobPlanRequest{ // Job: job, // Diff: true, // WriteRequest: api.WriteRequest{ // Region: tc.APIRegion, // Namespace: api.DefaultNamespace, // }, // } // buf := encodeReq(args) // // // Make the HTTP request // req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf) // require.NoError(t, err) // respW := httptest.NewRecorder() // // // Make the request // obj, err := s.Server.JobSpecificRequest(respW, req) // require.NoError(t, err) // // // Check the response // plan := obj.(structs.JobPlanResponse) // require.NotNil(t, plan.Annotations) // require.NotNil(t, plan.Diff) // }) // }) // } // } // // func TestHTTP_JobDispatch(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the parameterized job // job := mock.BatchJob() // job.ParameterizedJob = &structs.ParameterizedJobConfig{} // // args := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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", // Namespace: structs.DefaultNamespace, // }, // } // 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) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job and register it twice // job := mock.Job() // regReq := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // var regResp structs.JobRegisterResponse // if err := s.Agent.RPC("Job.Register", ®Req, ®Resp); err != nil { // t.Fatalf("err: %v", err) // } // // // Change the job to get a new version // job.Datacenters = append(job.Datacenters, "foo") // 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", // Namespace: structs.DefaultNamespace, // }, // } // 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 TestHTTP_JobStable(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job and register it twice // job := mock.Job() // regReq := structs.JobRegisterRequest{ // Job: job, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // 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.JobStabilityRequest{ // JobID: job.ID, // JobVersion: 0, // Stable: true, // WriteRequest: structs.WriteRequest{ // Region: "global", // Namespace: structs.DefaultNamespace, // }, // } // buf := encodeReq(args) // // // Make the HTTP request // req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", 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 // stableResp := obj.(structs.JobStabilityResponse) // if stableResp.Index == 0 { // t.Fatalf("bad: %v", stableResp) // } // // // 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"), // Namespace: helper.StringToPtr("foo"), // 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", // }, // }, // Affinities: []*api.Affinity{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // Weight: helper.Int8ToPtr(50), // }, // }, // Update: &api.UpdateStrategy{ // Stagger: helper.TimeToPtr(1 * time.Second), // MaxParallel: helper.IntToPtr(5), // HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), // MinHealthyTime: helper.TimeToPtr(1 * time.Minute), // HealthyDeadline: helper.TimeToPtr(3 * time.Minute), // ProgressDeadline: helper.TimeToPtr(3 * time.Minute), // AutoRevert: helper.BoolToPtr(false), // Canary: helper.IntToPtr(1), // }, // Spreads: []*api.Spread{ // { // Attribute: "${meta.rack}", // Weight: helper.Int8ToPtr(100), // SpreadTarget: []*api.SpreadTarget{ // { // Value: "r1", // Percent: 50, // }, // }, // }, // }, // 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", // }, // }, // Affinities: []*api.Affinity{ // { // LTarget: "x", // RTarget: "y", // Operand: "z", // Weight: helper.Int8ToPtr(100), // }, // }, // RestartPolicy: &api.RestartPolicy{ // Interval: helper.TimeToPtr(1 * time.Second), // Attempts: helper.IntToPtr(5), // Delay: helper.TimeToPtr(10 * time.Second), // Mode: helper.StringToPtr("delay"), // }, // ReschedulePolicy: &api.ReschedulePolicy{ // Interval: helper.TimeToPtr(12 * time.Hour), // Attempts: helper.IntToPtr(5), // DelayFunction: helper.StringToPtr("constant"), // Delay: helper.TimeToPtr(30 * time.Second), // Unlimited: helper.BoolToPtr(true), // MaxDelay: helper.TimeToPtr(20 * time.Minute), // }, // Migrate: &api.MigrateStrategy{ // MaxParallel: helper.IntToPtr(12), // HealthCheck: helper.StringToPtr("task_events"), // MinHealthyTime: helper.TimeToPtr(12 * time.Hour), // HealthyDeadline: helper.TimeToPtr(12 * time.Hour), // }, // Spreads: []*api.Spread{ // { // Attribute: "${node.datacenter}", // Weight: helper.Int8ToPtr(100), // SpreadTarget: []*api.SpreadTarget{ // { // Value: "dc1", // Percent: 100, // }, // }, // }, // }, // EphemeralDisk: &api.EphemeralDisk{ // SizeMB: helper.IntToPtr(100), // Sticky: helper.BoolToPtr(true), // Migrate: helper.BoolToPtr(true), // }, // Update: &api.UpdateStrategy{ // HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks), // MinHealthyTime: helper.TimeToPtr(2 * time.Minute), // HealthyDeadline: helper.TimeToPtr(5 * time.Minute), // ProgressDeadline: helper.TimeToPtr(5 * time.Minute), // AutoRevert: helper.BoolToPtr(true), // }, // Meta: map[string]string{ // "key": "value", // }, // Services: []*api.Service{ // { // Name: "groupserviceA", // Tags: []string{"a", "b"}, // CanaryTags: []string{"d", "e"}, // PortLabel: "1234", // Meta: map[string]string{ // "servicemeta": "foobar", // }, // CheckRestart: &api.CheckRestart{ // Limit: 4, // Grace: helper.TimeToPtr(11 * time.Second), // }, // Checks: []api.ServiceCheck{ // { // Id: "hello", // Name: "bar", // Type: "http", // Command: "foo", // Args: []string{"a", "b"}, // Path: "/check", // Protocol: "http", // PortLabel: "foo", // AddressMode: "driver", // GRPCService: "foo.Bar", // GRPCUseTLS: true, // Interval: 4 * time.Second, // Timeout: 2 * time.Second, // InitialStatus: "ok", // CheckRestart: &api.CheckRestart{ // Limit: 3, // IgnoreWarnings: true, // }, // TaskName: "task1", // }, // }, // Connect: &api.ConsulConnect{ // Native: false, // SidecarService: &api.ConsulSidecarService{ // Tags: []string{"f", "g"}, // Port: "9000", // }, // }, // }, // }, // 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", // }, // }, // Affinities: []*api.Affinity{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // Weight: helper.Int8ToPtr(50), // }, // }, // // Services: []*api.Service{ // { // Id: "id", // Name: "serviceA", // Tags: []string{"1", "2"}, // CanaryTags: []string{"3", "4"}, // PortLabel: "foo", // Meta: map[string]string{ // "servicemeta": "foobar", // }, // CheckRestart: &api.CheckRestart{ // Limit: 4, // Grace: helper.TimeToPtr(11 * time.Second), // }, // Checks: []api.ServiceCheck{ // { // Id: "hello", // Name: "bar", // Type: "http", // Command: "foo", // Args: []string{"a", "b"}, // Path: "/check", // Protocol: "http", // PortLabel: "foo", // AddressMode: "driver", // GRPCService: "foo.Bar", // GRPCUseTLS: true, // Interval: 4 * time.Second, // Timeout: 2 * time.Second, // InitialStatus: "ok", // CheckRestart: &api.CheckRestart{ // Limit: 3, // IgnoreWarnings: true, // }, // }, // { // Id: "check2id", // Name: "check2", // Type: "tcp", // PortLabel: "foo", // Interval: 4 * time.Second, // Timeout: 2 * time.Second, // }, // }, // }, // }, // 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, // }, // }, // }, // }, // Devices: []*api.RequestedDevice{ // { // Name: "nvidia/gpu", // Count: helper.Uint64ToPtr(4), // Constraints: []*api.Constraint{ // { // LTarget: "x", // RTarget: "y", // Operand: "z", // }, // }, // Affinities: []*api.Affinity{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // Weight: helper.Int8ToPtr(50), // }, // }, // }, // { // Name: "gpu", // Count: nil, // }, // }, // }, // Meta: map[string]string{ // "lol": "code", // }, // KillTimeout: helper.TimeToPtr(10 * time.Second), // KillSignal: "SIGQUIT", // LogConfig: &api.LogConfig{ // MaxFiles: helper.IntToPtr(10), // MaxFileSizeMB: helper.IntToPtr(100), // }, // Artifacts: []*api.TaskArtifact{ // { // GetterSource: helper.StringToPtr("source"), // GetterOptions: map[string]string{ // "a": "b", // }, // GetterMode: helper.StringToPtr("dir"), // 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"), // Envvars: helper.BoolToPtr(true), // VaultGrace: helper.TimeToPtr(3 * time.Second), // }, // }, // 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", // Namespace: "foo", // 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", // }, // }, // Affinities: []*structs.Affinity{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // Weight: 50, // }, // }, // Spreads: []*structs.Spread{ // { // Attribute: "${meta.rack}", // Weight: 100, // SpreadTarget: []*structs.SpreadTarget{ // { // Value: "r1", // Percent: 50, // }, // }, // }, // }, // 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", // }, // }, // Affinities: []*structs.Affinity{ // { // LTarget: "x", // RTarget: "y", // Operand: "z", // Weight: 100, // }, // }, // RestartPolicy: &structs.RestartPolicy{ // Interval: 1 * time.Second, // Attempts: 5, // Delay: 10 * time.Second, // Mode: "delay", // }, // Spreads: []*structs.Spread{ // { // Attribute: "${node.datacenter}", // Weight: 100, // SpreadTarget: []*structs.SpreadTarget{ // { // Value: "dc1", // Percent: 100, // }, // }, // }, // }, // ReschedulePolicy: &structs.ReschedulePolicy{ // Interval: 12 * time.Hour, // Attempts: 5, // DelayFunction: "constant", // Delay: 30 * time.Second, // Unlimited: true, // MaxDelay: 20 * time.Minute, // }, // Migrate: &structs.MigrateStrategy{ // MaxParallel: 12, // HealthCheck: "task_events", // MinHealthyTime: 12 * time.Hour, // HealthyDeadline: 12 * time.Hour, // }, // EphemeralDisk: &structs.EphemeralDisk{ // SizeMB: 100, // Sticky: true, // Migrate: true, // }, // Update: &structs.UpdateStrategy{ // Stagger: 1 * time.Second, // MaxParallel: 5, // HealthCheck: structs.UpdateStrategyHealthCheck_Checks, // MinHealthyTime: 2 * time.Minute, // HealthyDeadline: 5 * time.Minute, // ProgressDeadline: 5 * time.Minute, // AutoRevert: true, // AutoPromote: false, // Canary: 1, // }, // Meta: map[string]string{ // "key": "value", // }, // Services: []*structs.Service{ // { // Name: "groupserviceA", // Tags: []string{"a", "b"}, // CanaryTags: []string{"d", "e"}, // PortLabel: "1234", // AddressMode: "auto", // Meta: map[string]string{ // "servicemeta": "foobar", // }, // Checks: []*structs.ServiceCheck{ // { // Name: "bar", // Type: "http", // Command: "foo", // Args: []string{"a", "b"}, // Path: "/check", // Protocol: "http", // PortLabel: "foo", // AddressMode: "driver", // GRPCService: "foo.Bar", // GRPCUseTLS: true, // Interval: 4 * time.Second, // Timeout: 2 * time.Second, // InitialStatus: "ok", // CheckRestart: &structs.CheckRestart{ // Grace: 11 * time.Second, // Limit: 3, // IgnoreWarnings: true, // }, // TaskName: "task1", // }, // }, // Connect: &structs.ConsulConnect{ // Native: false, // SidecarService: &structs.ConsulSidecarService{ // Tags: []string{"f", "g"}, // Port: "9000", // }, // }, // }, // }, // 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", // }, // }, // Affinities: []*structs.Affinity{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // Weight: 50, // }, // }, // Env: map[string]string{ // "hello": "world", // }, // Services: []*structs.Service{ // { // Name: "serviceA", // Tags: []string{"1", "2"}, // CanaryTags: []string{"3", "4"}, // PortLabel: "foo", // AddressMode: "auto", // Meta: map[string]string{ // "servicemeta": "foobar", // }, // Checks: []*structs.ServiceCheck{ // { // Name: "bar", // Type: "http", // Command: "foo", // Args: []string{"a", "b"}, // Path: "/check", // Protocol: "http", // PortLabel: "foo", // AddressMode: "driver", // Interval: 4 * time.Second, // Timeout: 2 * time.Second, // InitialStatus: "ok", // GRPCService: "foo.Bar", // GRPCUseTLS: true, // CheckRestart: &structs.CheckRestart{ // Limit: 3, // Grace: 11 * time.Second, // IgnoreWarnings: true, // }, // }, // { // Name: "check2", // Type: "tcp", // PortLabel: "foo", // Interval: 4 * time.Second, // Timeout: 2 * time.Second, // CheckRestart: &structs.CheckRestart{ // Limit: 4, // Grace: 11 * time.Second, // }, // }, // }, // }, // }, // 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, // }, // }, // }, // }, // Devices: []*structs.RequestedDevice{ // { // Name: "nvidia/gpu", // Count: 4, // Constraints: []*structs.Constraint{ // { // LTarget: "x", // RTarget: "y", // Operand: "z", // }, // }, // Affinities: []*structs.Affinity{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // Weight: 50, // }, // }, // }, // { // Name: "gpu", // Count: 1, // }, // }, // }, // Meta: map[string]string{ // "lol": "code", // }, // KillTimeout: 10 * time.Second, // KillSignal: "SIGQUIT", // LogConfig: &structs.LogConfig{ // MaxFiles: 10, // MaxFileSizeMB: 100, // }, // Artifacts: []*structs.TaskArtifact{ // { // GetterSource: "source", // GetterOptions: map[string]string{ // "a": "b", // }, // GetterMode: "dir", // 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", // Envvars: true, // VaultGrace: 3 * time.Second, // }, // }, // DispatchPayload: &structs.DispatchPayloadConfig{ // File: "fileA", // }, // }, // }, // }, // }, // // VaultToken: "token", // } // // structsJob := ApiJobToStructJob(apiJob) // // if diff := pretty.Diff(expected, structsJob); len(diff) > 0 { // t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) // } // // systemAPIJob := &api.Job{ // Stop: helper.BoolToPtr(true), // Region: helper.StringToPtr("global"), // Namespace: helper.StringToPtr("foo"), // ID: helper.StringToPtr("foo"), // ParentID: helper.StringToPtr("lol"), // Name: helper.StringToPtr("name"), // Type: helper.StringToPtr("system"), // Priority: helper.IntToPtr(50), // AllAtOnce: helper.BoolToPtr(true), // Datacenters: []string{"dc1", "dc2"}, // Constraints: []*api.Constraint{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // }, // }, // 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", // }, // }, // 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), // KillSignal: "SIGQUIT", // LogConfig: &api.LogConfig{ // MaxFiles: helper.IntToPtr(10), // MaxFileSizeMB: helper.IntToPtr(100), // }, // Artifacts: []*api.TaskArtifact{ // { // GetterSource: helper.StringToPtr("source"), // GetterOptions: map[string]string{ // "a": "b", // }, // GetterMode: helper.StringToPtr("dir"), // RelativeDest: helper.StringToPtr("dest"), // }, // }, // DispatchPayload: &api.DispatchPayloadConfig{ // File: "fileA", // }, // }, // }, // }, // }, // 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), // } // // expectedSystemJob := &structs.Job{ // Stop: true, // Region: "global", // Namespace: "foo", // ID: "foo", // ParentID: "lol", // Name: "name", // Type: "system", // Priority: 50, // AllAtOnce: true, // Datacenters: []string{"dc1", "dc2"}, // Constraints: []*structs.Constraint{ // { // LTarget: "a", // RTarget: "b", // Operand: "c", // }, // }, // 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", // }, // 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, // KillSignal: "SIGQUIT", // LogConfig: &structs.LogConfig{ // MaxFiles: 10, // MaxFileSizeMB: 100, // }, // Artifacts: []*structs.TaskArtifact{ // { // GetterSource: "source", // GetterOptions: map[string]string{ // "a": "b", // }, // GetterMode: "dir", // RelativeDest: "dest", // }, // }, // DispatchPayload: &structs.DispatchPayloadConfig{ // File: "fileA", // }, // }, // }, // }, // }, // } // // systemStructsJob := ApiJobToStructJob(systemAPIJob) // // if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 { // t.Fatalf("bad:\n%s", strings.Join(diff, "\n")) // } // } // // func TestJobs_ApiJobToStructsJobUpdate(t *testing.T) { // apiJob := &api.Job{ // Update: &api.UpdateStrategy{ // Stagger: helper.TimeToPtr(1 * time.Second), // MaxParallel: helper.IntToPtr(5), // HealthCheck: helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual), // MinHealthyTime: helper.TimeToPtr(1 * time.Minute), // HealthyDeadline: helper.TimeToPtr(3 * time.Minute), // ProgressDeadline: helper.TimeToPtr(3 * time.Minute), // AutoRevert: helper.BoolToPtr(false), // AutoPromote: nil, // Canary: helper.IntToPtr(1), // }, // TaskGroups: []*api.TaskGroup{ // { // Update: &api.UpdateStrategy{ // Canary: helper.IntToPtr(2), // AutoRevert: helper.BoolToPtr(true), // }, // }, { // Update: &api.UpdateStrategy{ // Canary: helper.IntToPtr(3), // AutoPromote: helper.BoolToPtr(true), // }, // }, // }, // } // // structsJob := ApiJobToStructJob(apiJob) // // // Update has been moved from job down to the groups // jobUpdate := structs.UpdateStrategy{ // Stagger: 1000000000, // MaxParallel: 5, // HealthCheck: "", // MinHealthyTime: 0, // HealthyDeadline: 0, // ProgressDeadline: 0, // AutoRevert: false, // AutoPromote: false, // Canary: 0, // } // // // But the groups inherit settings from the job update // group1 := structs.UpdateStrategy{ // Stagger: 1000000000, // MaxParallel: 5, // HealthCheck: "manual", // MinHealthyTime: 60000000000, // HealthyDeadline: 180000000000, // ProgressDeadline: 180000000000, // AutoRevert: true, // AutoPromote: false, // Canary: 2, // } // // group2 := structs.UpdateStrategy{ // Stagger: 1000000000, // MaxParallel: 5, // HealthCheck: "manual", // MinHealthyTime: 60000000000, // HealthyDeadline: 180000000000, // ProgressDeadline: 180000000000, // AutoRevert: false, // AutoPromote: true, // Canary: 3, // } // // require.Equal(t, jobUpdate, structsJob.Update) // require.Equal(t, group1, *structsJob.TaskGroups[0].Update) // require.Equal(t, group2, *structsJob.TaskGroups[1].Update) // } // // // TestHTTP_JobValidate_SystemMigrate asserts that a system job with a migrate // // stanza fails to validate but does not panic (see #5477). // func TestHTTP_JobValidate_SystemMigrate(t *testing.T) { // t.Parallel() // httpTest(t, nil, func(s *TestAgent) { // // Create the job // job := &api.Job{ // Region: helper.StringToPtr("global"), // Datacenters: []string{"dc1"}, // ID: helper.StringToPtr("systemmigrate"), // Name: helper.StringToPtr("systemmigrate"), // TaskGroups: []*api.TaskGroup{ // {Name: helper.StringToPtr("web")}, // }, // // // System job... // Type: helper.StringToPtr("system"), // // // ...with an empty migrate stanza // Migrate: &api.MigrateStrategy{}, // } // // args := api.JobValidateRequest{ // Job: job, // WriteRequest: api.WriteRequest{Region: "global"}, // } // buf := encodeReq(args) // // // Make the HTTP request // req, err := http.NewRequest("PUT", "/v1/validate/job", buf) // require.NoError(t, err) // respW := httptest.NewRecorder() // // // Make the request // obj, err := s.Server.ValidateJobRequest(respW, req) // require.NoError(t, err) // // // Check the response // resp := obj.(structs.JobValidateResponse) // require.Contains(t, resp.Error, `Job type "system" does not allow migrate block`) // }) // }