Commit 7515f778 authored by Alex Dadgar's avatar Alex Dadgar
Browse files

Thread through warnings about deprecations

parent d606e98a
Showing with 385 additions and 83 deletions
+385 -83
...@@ -27,7 +27,7 @@ func TestEvaluations_List(t *testing.T) { ...@@ -27,7 +27,7 @@ func TestEvaluations_List(t *testing.T) {
// Register a job. This will create an evaluation. // Register a job. This will create an evaluation.
jobs := c.Jobs() jobs := c.Jobs()
job := testJob() job := testJob()
evalID, wm, err := jobs.Register(job, nil) resp, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
...@@ -43,8 +43,8 @@ func TestEvaluations_List(t *testing.T) { ...@@ -43,8 +43,8 @@ func TestEvaluations_List(t *testing.T) {
// if the eval fails fast there can be more than 1 // if the eval fails fast there can be more than 1
// but they are in order of most recent first, so look at the last one // but they are in order of most recent first, so look at the last one
idx := len(result) - 1 idx := len(result) - 1
if len(result) == 0 || result[idx].ID != evalID { if len(result) == 0 || result[idx].ID != resp.EvalID {
t.Fatalf("expected eval (%s), got: %#v", evalID, result[idx]) t.Fatalf("expected eval (%s), got: %#v", resp.EvalID, result[idx])
} }
} }
...@@ -68,21 +68,21 @@ func TestEvaluations_PrefixList(t *testing.T) { ...@@ -68,21 +68,21 @@ func TestEvaluations_PrefixList(t *testing.T) {
// Register a job. This will create an evaluation. // Register a job. This will create an evaluation.
jobs := c.Jobs() jobs := c.Jobs()
job := testJob() job := testJob()
evalID, wm, err := jobs.Register(job, nil) resp, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
// Check the evaluations again // Check the evaluations again
result, qm, err = e.PrefixList(evalID[:4]) result, qm, err = e.PrefixList(resp.EvalID[:4])
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
assertQueryMeta(t, qm) assertQueryMeta(t, qm)
// Check if we have the right list // Check if we have the right list
if len(result) != 1 || result[0].ID != evalID { if len(result) != 1 || result[0].ID != resp.EvalID {
t.Fatalf("bad: %#v", result) t.Fatalf("bad: %#v", result)
} }
} }
...@@ -101,22 +101,22 @@ func TestEvaluations_Info(t *testing.T) { ...@@ -101,22 +101,22 @@ func TestEvaluations_Info(t *testing.T) {
// Register a job. Creates a new evaluation. // Register a job. Creates a new evaluation.
jobs := c.Jobs() jobs := c.Jobs()
job := testJob() job := testJob()
evalID, wm, err := jobs.Register(job, nil) resp, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
// Try looking up by the new eval ID // Try looking up by the new eval ID
result, qm, err := e.Info(evalID, nil) result, qm, err := e.Info(resp.EvalID, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
assertQueryMeta(t, qm) assertQueryMeta(t, qm)
// Check that we got the right result // Check that we got the right result
if result == nil || result.ID != evalID { if result == nil || result.ID != resp.EvalID {
t.Fatalf("expected eval %q, got: %#v", evalID, result) t.Fatalf("expected eval %q, got: %#v", resp.EvalID, result)
} }
} }
......
...@@ -51,20 +51,20 @@ func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *Write ...@@ -51,20 +51,20 @@ func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *Write
// Register is used to register a new job. It returns the ID // Register is used to register a new job. It returns the ID
// of the evaluation, along with any errors encountered. // of the evaluation, along with any errors encountered.
func (j *Jobs) Register(job *Job, q *WriteOptions) (string, *WriteMeta, error) { func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
var resp JobRegisterResponse var resp JobRegisterResponse
req := &RegisterJobRequest{Job: job} req := &RegisterJobRequest{Job: job}
wm, err := j.client.write("/v1/jobs", req, &resp, q) wm, err := j.client.write("/v1/jobs", req, &resp, q)
if err != nil { if err != nil {
return "", nil, err return nil, nil, err
} }
return resp.EvalID, wm, nil return &resp, wm, nil
} }
// EnforceRegister is used to register a job enforcing its job modify index. // EnforceRegister is used to register a job enforcing its job modify index.
func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (string, *WriteMeta, error) { func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
var resp JobRegisterResponse var resp JobRegisterResponse
...@@ -75,9 +75,9 @@ func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (s ...@@ -75,9 +75,9 @@ func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (s
} }
wm, err := j.client.write("/v1/jobs", req, &resp, q) wm, err := j.client.write("/v1/jobs", req, &resp, q)
if err != nil { if err != nil {
return "", nil, err return nil, nil, err
} }
return resp.EvalID, wm, nil return &resp, wm, nil
} }
// List is used to list all of the existing jobs. // List is used to list all of the existing jobs.
...@@ -659,6 +659,10 @@ type JobValidateResponse struct { ...@@ -659,6 +659,10 @@ type JobValidateResponse struct {
// Error is a string version of any error that may have occured // Error is a string version of any error that may have occured
Error string Error string
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
} }
// JobRevertRequest is used to revert a job to a prior version. // JobRevertRequest is used to revert a job to a prior version.
...@@ -700,6 +704,11 @@ type JobRegisterResponse struct { ...@@ -700,6 +704,11 @@ type JobRegisterResponse struct {
EvalID string EvalID string
EvalCreateIndex uint64 EvalCreateIndex uint64
JobModifyIndex uint64 JobModifyIndex uint64
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
QueryMeta QueryMeta
} }
...@@ -724,6 +733,10 @@ type JobPlanResponse struct { ...@@ -724,6 +733,10 @@ type JobPlanResponse struct {
Annotations *PlanAnnotations Annotations *PlanAnnotations
FailedTGAllocs map[string]*AllocationMetric FailedTGAllocs map[string]*AllocationMetric
NextPeriodicLaunch time.Time NextPeriodicLaunch time.Time
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
} }
type JobDiff struct { type JobDiff struct {
......
...@@ -31,11 +31,11 @@ func TestJobs_Register(t *testing.T) { ...@@ -31,11 +31,11 @@ func TestJobs_Register(t *testing.T) {
// Create a job and attempt to register it // Create a job and attempt to register it
job := testJob() job := testJob()
eval, wm, err := jobs.Register(job, nil) resp2, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if eval == "" { if resp2 == nil || resp2.EvalID == "" {
t.Fatalf("missing eval id") t.Fatalf("missing eval id")
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
...@@ -209,8 +209,7 @@ func TestJobs_Canonicalize(t *testing.T) { ...@@ -209,8 +209,7 @@ func TestJobs_Canonicalize(t *testing.T) {
Datacenters: []string{"dc1"}, Datacenters: []string{"dc1"},
Type: helper.StringToPtr("service"), Type: helper.StringToPtr("service"),
Update: &UpdateStrategy{ Update: &UpdateStrategy{
Stagger: 10 * time.Second, MaxParallel: helper.IntToPtr(1),
MaxParallel: 1,
}, },
TaskGroups: []*TaskGroup{ TaskGroups: []*TaskGroup{
{ {
...@@ -294,8 +293,12 @@ func TestJobs_Canonicalize(t *testing.T) { ...@@ -294,8 +293,12 @@ func TestJobs_Canonicalize(t *testing.T) {
JobModifyIndex: helper.Uint64ToPtr(0), JobModifyIndex: helper.Uint64ToPtr(0),
Datacenters: []string{"dc1"}, Datacenters: []string{"dc1"},
Update: &UpdateStrategy{ Update: &UpdateStrategy{
Stagger: 10 * time.Second, MaxParallel: helper.IntToPtr(1),
MaxParallel: 1, HealthCheck: helper.StringToPtr("checks"),
MinHealthyTime: helper.TimeToPtr(10 * time.Second),
HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
AutoRevert: helper.BoolToPtr(false),
Canary: helper.IntToPtr(0),
}, },
TaskGroups: []*TaskGroup{ TaskGroups: []*TaskGroup{
{ {
...@@ -312,6 +315,15 @@ func TestJobs_Canonicalize(t *testing.T) { ...@@ -312,6 +315,15 @@ func TestJobs_Canonicalize(t *testing.T) {
Migrate: helper.BoolToPtr(false), Migrate: helper.BoolToPtr(false),
SizeMB: helper.IntToPtr(300), SizeMB: helper.IntToPtr(300),
}, },
Update: &UpdateStrategy{
MaxParallel: helper.IntToPtr(1),
HealthCheck: helper.StringToPtr("checks"),
MinHealthyTime: helper.TimeToPtr(10 * time.Second),
HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
AutoRevert: helper.BoolToPtr(false),
Canary: helper.IntToPtr(0),
},
Tasks: []*Task{ Tasks: []*Task{
{ {
Name: "redis", Name: "redis",
...@@ -406,6 +418,138 @@ func TestJobs_Canonicalize(t *testing.T) { ...@@ -406,6 +418,138 @@ func TestJobs_Canonicalize(t *testing.T) {
}, },
}, },
}, },
{
name: "update_merge",
input: &Job{
Name: helper.StringToPtr("foo"),
ID: helper.StringToPtr("bar"),
ParentID: helper.StringToPtr("lol"),
Update: &UpdateStrategy{
MaxParallel: helper.IntToPtr(1),
HealthCheck: helper.StringToPtr("checks"),
MinHealthyTime: helper.TimeToPtr(10 * time.Second),
HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
AutoRevert: helper.BoolToPtr(false),
Canary: helper.IntToPtr(0),
},
TaskGroups: []*TaskGroup{
{
Name: helper.StringToPtr("bar"),
Update: &UpdateStrategy{
MaxParallel: helper.IntToPtr(2),
HealthCheck: helper.StringToPtr("manual"),
MinHealthyTime: helper.TimeToPtr(1 * time.Second),
AutoRevert: helper.BoolToPtr(true),
Canary: helper.IntToPtr(1),
},
Tasks: []*Task{
{
Name: "task1",
},
},
},
{
Name: helper.StringToPtr("baz"),
Tasks: []*Task{
{
Name: "task1",
},
},
},
},
},
expected: &Job{
ID: helper.StringToPtr("bar"),
Name: helper.StringToPtr("foo"),
Region: helper.StringToPtr("global"),
Type: helper.StringToPtr("service"),
ParentID: helper.StringToPtr("lol"),
Priority: helper.IntToPtr(50),
AllAtOnce: helper.BoolToPtr(false),
VaultToken: helper.StringToPtr(""),
Stop: helper.BoolToPtr(false),
Stable: helper.BoolToPtr(false),
Version: helper.Uint64ToPtr(0),
Status: helper.StringToPtr(""),
StatusDescription: helper.StringToPtr(""),
CreateIndex: helper.Uint64ToPtr(0),
ModifyIndex: helper.Uint64ToPtr(0),
JobModifyIndex: helper.Uint64ToPtr(0),
Update: &UpdateStrategy{
MaxParallel: helper.IntToPtr(1),
HealthCheck: helper.StringToPtr("checks"),
MinHealthyTime: helper.TimeToPtr(10 * time.Second),
HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
AutoRevert: helper.BoolToPtr(false),
Canary: helper.IntToPtr(0),
},
TaskGroups: []*TaskGroup{
{
Name: helper.StringToPtr("bar"),
Count: helper.IntToPtr(1),
EphemeralDisk: &EphemeralDisk{
Sticky: helper.BoolToPtr(false),
Migrate: helper.BoolToPtr(false),
SizeMB: helper.IntToPtr(300),
},
RestartPolicy: &RestartPolicy{
Delay: helper.TimeToPtr(15 * time.Second),
Attempts: helper.IntToPtr(2),
Interval: helper.TimeToPtr(1 * time.Minute),
Mode: helper.StringToPtr("delay"),
},
Update: &UpdateStrategy{
MaxParallel: helper.IntToPtr(2),
HealthCheck: helper.StringToPtr("manual"),
MinHealthyTime: helper.TimeToPtr(1 * time.Second),
HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
AutoRevert: helper.BoolToPtr(true),
Canary: helper.IntToPtr(1),
},
Tasks: []*Task{
{
Name: "task1",
LogConfig: DefaultLogConfig(),
Resources: MinResources(),
KillTimeout: helper.TimeToPtr(5 * time.Second),
},
},
},
{
Name: helper.StringToPtr("baz"),
Count: helper.IntToPtr(1),
EphemeralDisk: &EphemeralDisk{
Sticky: helper.BoolToPtr(false),
Migrate: helper.BoolToPtr(false),
SizeMB: helper.IntToPtr(300),
},
RestartPolicy: &RestartPolicy{
Delay: helper.TimeToPtr(15 * time.Second),
Attempts: helper.IntToPtr(2),
Interval: helper.TimeToPtr(1 * time.Minute),
Mode: helper.StringToPtr("delay"),
},
Update: &UpdateStrategy{
MaxParallel: helper.IntToPtr(1),
HealthCheck: helper.StringToPtr("checks"),
MinHealthyTime: helper.TimeToPtr(10 * time.Second),
HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
AutoRevert: helper.BoolToPtr(false),
Canary: helper.IntToPtr(0),
},
Tasks: []*Task{
{
Name: "task1",
LogConfig: DefaultLogConfig(),
Resources: MinResources(),
KillTimeout: helper.TimeToPtr(5 * time.Second),
},
},
},
},
},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
...@@ -438,17 +582,17 @@ func TestJobs_EnforceRegister(t *testing.T) { ...@@ -438,17 +582,17 @@ func TestJobs_EnforceRegister(t *testing.T) {
// Create a job and attempt to register it with an incorrect index. // Create a job and attempt to register it with an incorrect index.
job := testJob() job := testJob()
eval, wm, err := jobs.EnforceRegister(job, 10, nil) resp2, wm, err := jobs.EnforceRegister(job, 10, nil)
if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
t.Fatalf("expected enforcement error: %v", err) t.Fatalf("expected enforcement error: %v", err)
} }
// Register // Register
eval, wm, err = jobs.EnforceRegister(job, 0, nil) resp2, wm, err = jobs.EnforceRegister(job, 0, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if eval == "" { if resp2 == nil || resp2.EvalID == "" {
t.Fatalf("missing eval id") t.Fatalf("missing eval id")
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
...@@ -471,17 +615,17 @@ func TestJobs_EnforceRegister(t *testing.T) { ...@@ -471,17 +615,17 @@ func TestJobs_EnforceRegister(t *testing.T) {
curIndex := resp[0].JobModifyIndex curIndex := resp[0].JobModifyIndex
// Fail at incorrect index // Fail at incorrect index
eval, wm, err = jobs.EnforceRegister(job, 123456, nil) resp2, wm, err = jobs.EnforceRegister(job, 123456, nil)
if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) { if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
t.Fatalf("expected enforcement error: %v", err) t.Fatalf("expected enforcement error: %v", err)
} }
// Works at correct index // Works at correct index
eval, wm, err = jobs.EnforceRegister(job, curIndex, nil) resp3, wm, err := jobs.EnforceRegister(job, curIndex, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if eval == "" { if resp3 == nil || resp3.EvalID == "" {
t.Fatalf("missing eval id") t.Fatalf("missing eval id")
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
...@@ -494,20 +638,20 @@ func TestJobs_Revert(t *testing.T) { ...@@ -494,20 +638,20 @@ func TestJobs_Revert(t *testing.T) {
// Register twice // Register twice
job := testJob() job := testJob()
eval, wm, err := jobs.Register(job, nil) resp, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if eval == "" { if resp == nil || resp.EvalID == "" {
t.Fatalf("missing eval id") t.Fatalf("missing eval id")
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
eval, wm, err = jobs.Register(job, nil) resp, wm, err = jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if eval == "" { if resp == nil || resp.EvalID == "" {
t.Fatalf("missing eval id") t.Fatalf("missing eval id")
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
...@@ -717,7 +861,7 @@ func TestJobs_Evaluations(t *testing.T) { ...@@ -717,7 +861,7 @@ func TestJobs_Evaluations(t *testing.T) {
// Insert a job. This also creates an evaluation so we should // Insert a job. This also creates an evaluation so we should
// be able to query that out after. // be able to query that out after.
job := testJob() job := testJob()
evalID, wm, err := jobs.Register(job, nil) resp, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
...@@ -733,8 +877,8 @@ func TestJobs_Evaluations(t *testing.T) { ...@@ -733,8 +877,8 @@ func TestJobs_Evaluations(t *testing.T) {
// Check that we got the evals back, evals are in order most recent to least recent // Check that we got the evals back, evals are in order most recent to least recent
// so the last eval is the original registered eval // so the last eval is the original registered eval
idx := len(evals) - 1 idx := len(evals) - 1
if n := len(evals); n == 0 || evals[idx].ID != evalID { if n := len(evals); n == 0 || evals[idx].ID != resp.EvalID {
t.Fatalf("expected >= 1 eval (%s), got: %#v", evalID, evals[idx]) t.Fatalf("expected >= 1 eval (%s), got: %#v", resp.EvalID, evals[idx])
} }
} }
...@@ -895,11 +1039,11 @@ func TestJobs_Plan(t *testing.T) { ...@@ -895,11 +1039,11 @@ func TestJobs_Plan(t *testing.T) {
// Create a job and attempt to register it // Create a job and attempt to register it
job := testJob() job := testJob()
eval, wm, err := jobs.Register(job, nil) resp, wm, err := jobs.Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if eval == "" { if resp == nil || resp.EvalID == "" {
t.Fatalf("missing eval id") t.Fatalf("missing eval id")
} }
assertWriteMeta(t, wm) assertWriteMeta(t, wm)
......
...@@ -708,16 +708,16 @@ func TestHTTP_PeriodicForce(t *testing.T) { ...@@ -708,16 +708,16 @@ func TestHTTP_PeriodicForce(t *testing.T) {
func TestHTTP_JobPlan(t *testing.T) { func TestHTTP_JobPlan(t *testing.T) {
httpTest(t, nil, func(s *TestServer) { httpTest(t, nil, func(s *TestServer) {
// Create the job // Create the job
job := mock.Job() job := api.MockJob()
args := structs.JobPlanRequest{ args := api.JobPlanRequest{
Job: job, Job: job,
Diff: true, Diff: true,
WriteRequest: structs.WriteRequest{Region: "global"}, WriteRequest: api.WriteRequest{Region: "global"},
} }
buf := encodeReq(args) buf := encodeReq(args)
// Make the HTTP request // Make the HTTP request
req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/plan", buf) req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
......
...@@ -102,11 +102,11 @@ func TestAllocStatusCommand_Run(t *testing.T) { ...@@ -102,11 +102,11 @@ func TestAllocStatusCommand_Run(t *testing.T) {
jobID := "job1_sfx" jobID := "job1_sfx"
job1 := testJob(jobID) job1 := testJob(jobID)
evalId1, _, err := client.Jobs().Register(job1, nil) resp, _, err := client.Jobs().Register(job1, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if code := waitForSuccess(ui, client, fullId, t, evalId1); code != 0 { if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code) t.Fatalf("status code non zero saw %d", code)
} }
// get an alloc id // get an alloc id
......
...@@ -178,7 +178,7 @@ func TestMonitor_Monitor(t *testing.T) { ...@@ -178,7 +178,7 @@ func TestMonitor_Monitor(t *testing.T) {
// Submit a job - this creates a new evaluation we can monitor // Submit a job - this creates a new evaluation we can monitor
job := testJob("job1") job := testJob("job1")
evalID, _, err := client.Jobs().Register(job, nil) resp, _, err := client.Jobs().Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
...@@ -188,7 +188,7 @@ func TestMonitor_Monitor(t *testing.T) { ...@@ -188,7 +188,7 @@ func TestMonitor_Monitor(t *testing.T) {
doneCh := make(chan struct{}) doneCh := make(chan struct{})
go func() { go func() {
defer close(doneCh) defer close(doneCh)
code = mon.monitor(evalID, false) code = mon.monitor(resp.EvalID, false)
}() }()
// Wait for completion // Wait for completion
...@@ -206,7 +206,7 @@ func TestMonitor_Monitor(t *testing.T) { ...@@ -206,7 +206,7 @@ func TestMonitor_Monitor(t *testing.T) {
// Check the output // Check the output
out := ui.OutputWriter.String() out := ui.OutputWriter.String()
if !strings.Contains(out, evalID) { if !strings.Contains(out, resp.EvalID) {
t.Fatalf("missing eval\n\n%s", out) t.Fatalf("missing eval\n\n%s", out)
} }
if !strings.Contains(out, "finished with status") { if !strings.Contains(out, "finished with status") {
...@@ -224,7 +224,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) { ...@@ -224,7 +224,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) {
// Submit a job - this creates a new evaluation we can monitor // Submit a job - this creates a new evaluation we can monitor
job := testJob("job1") job := testJob("job1")
evalID, _, err := client.Jobs().Register(job, nil) resp, _, err := client.Jobs().Register(job, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
...@@ -234,7 +234,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) { ...@@ -234,7 +234,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) {
doneCh := make(chan struct{}) doneCh := make(chan struct{})
go func() { go func() {
defer close(doneCh) defer close(doneCh)
code = mon.monitor(evalID[:8], true) code = mon.monitor(resp.EvalID[:8], true)
}() }()
// Wait for completion // Wait for completion
...@@ -252,10 +252,10 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) { ...@@ -252,10 +252,10 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) {
// Check the output // Check the output
out := ui.OutputWriter.String() out := ui.OutputWriter.String()
if !strings.Contains(out, evalID[:8]) { if !strings.Contains(out, resp.EvalID[:8]) {
t.Fatalf("missing eval\n\n%s", out) t.Fatalf("missing eval\n\n%s", out)
} }
if strings.Contains(out, evalID) { if strings.Contains(out, resp.EvalID) {
t.Fatalf("expected truncated eval id, got: %s", out) t.Fatalf("expected truncated eval id, got: %s", out)
} }
if !strings.Contains(out, "finished with status") { if !strings.Contains(out, "finished with status") {
...@@ -263,7 +263,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) { ...@@ -263,7 +263,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) {
} }
// Fail on identifier with too few characters // Fail on identifier with too few characters
code = mon.monitor(evalID[:1], true) code = mon.monitor(resp.EvalID[:1], true)
if code != 1 { if code != 1 {
t.Fatalf("expect exit 1, got: %d", code) t.Fatalf("expect exit 1, got: %d", code)
} }
...@@ -272,7 +272,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) { ...@@ -272,7 +272,7 @@ func TestMonitor_MonitorWithPrefix(t *testing.T) {
} }
ui.ErrorWriter.Reset() ui.ErrorWriter.Reset()
code = mon.monitor(evalID[:3], true) code = mon.monitor(resp.EvalID[:3], true)
if code != 2 { if code != 2 {
t.Fatalf("expect exit 2, got: %d", code) t.Fatalf("expect exit 2, got: %d", code)
} }
......
...@@ -134,6 +134,12 @@ func (c *PlanCommand) Run(args []string) int { ...@@ -134,6 +134,12 @@ func (c *PlanCommand) Run(args []string) int {
c.Ui.Output(c.Colorize().Color(formatDryRun(resp, job))) c.Ui.Output(c.Colorize().Color(formatDryRun(resp, job)))
c.Ui.Output("") c.Ui.Output("")
// Print any warnings if there are any
if resp.Warnings != "" {
c.Ui.Output(
c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings)))
}
// Print the job index info // Print the job index info
c.Ui.Output(c.Colorize().Color(formatJobModifyIndex(resp.JobModifyIndex, path))) c.Ui.Output(c.Colorize().Color(formatJobModifyIndex(resp.JobModifyIndex, path)))
return getExitCode(resp) return getExitCode(resp)
......
...@@ -165,19 +165,6 @@ func (c *RunCommand) Run(args []string) int { ...@@ -165,19 +165,6 @@ func (c *RunCommand) Run(args []string) int {
job.VaultToken = helper.StringToPtr(vaultToken) job.VaultToken = helper.StringToPtr(vaultToken)
} }
// COMPAT 0.4.1 -> 0.5 Remove in 0.6
OUTSIDE:
for _, tg := range job.TaskGroups {
for _, task := range tg.Tasks {
if task.Resources != nil {
if task.Resources.DiskMB != nil {
c.Ui.Error("WARNING: disk attribute is deprecated in the resources block. See https://www.nomadproject.io/docs/job-specification/ephemeral_disk.html")
break OUTSIDE
}
}
}
}
if output { if output {
req := api.RegisterJobRequest{Job: job} req := api.RegisterJobRequest{Job: job}
buf, err := json.MarshalIndent(req, "", " ") buf, err := json.MarshalIndent(req, "", " ")
...@@ -198,11 +185,11 @@ OUTSIDE: ...@@ -198,11 +185,11 @@ OUTSIDE:
} }
// Submit the job // Submit the job
var evalID string var resp *api.JobRegisterResponse
if enforce { if enforce {
evalID, _, err = client.Jobs().EnforceRegister(job, checkIndex, nil) resp, _, err = client.Jobs().EnforceRegister(job, checkIndex, nil)
} else { } else {
evalID, _, err = client.Jobs().Register(job, nil) resp, _, err = client.Jobs().Register(job, nil)
} }
if err != nil { if err != nil {
if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) { if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) {
...@@ -220,6 +207,14 @@ OUTSIDE: ...@@ -220,6 +207,14 @@ OUTSIDE:
return 1 return 1
} }
// Print any warnings if there are any
if resp.Warnings != "" {
c.Ui.Output(
c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings)))
}
evalID := resp.EvalID
// Check if we should enter monitor mode // Check if we should enter monitor mode
if detach || periodic || paramjob { if detach || periodic || paramjob {
c.Ui.Output("Job registration successful") c.Ui.Output("Job registration successful")
......
...@@ -37,20 +37,20 @@ func TestStatusCommand_Run(t *testing.T) { ...@@ -37,20 +37,20 @@ func TestStatusCommand_Run(t *testing.T) {
// Register two jobs // Register two jobs
job1 := testJob("job1_sfx") job1 := testJob("job1_sfx")
evalId1, _, err := client.Jobs().Register(job1, nil) resp, _, err := client.Jobs().Register(job1, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if code := waitForSuccess(ui, client, fullId, t, evalId1); code != 0 { if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code) t.Fatalf("status code non zero saw %d", code)
} }
job2 := testJob("job2_sfx") job2 := testJob("job2_sfx")
evalId2, _, err := client.Jobs().Register(job2, nil) resp2, _, err := client.Jobs().Register(job2, nil)
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if code := waitForSuccess(ui, client, fullId, t, evalId2); code != 0 { if code := waitForSuccess(ui, client, fullId, t, resp2.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code) t.Fatalf("status code non zero saw %d", code)
} }
...@@ -149,7 +149,7 @@ func TestStatusCommand_Run(t *testing.T) { ...@@ -149,7 +149,7 @@ func TestStatusCommand_Run(t *testing.T) {
if strings.Contains(out, "Allocations") { if strings.Contains(out, "Allocations") {
t.Fatalf("should not dump allocations") t.Fatalf("should not dump allocations")
} }
if strings.Contains(out, evalId1) { if strings.Contains(out, resp.EvalID) {
t.Fatalf("should not contain full identifiers, got %s", out) t.Fatalf("should not contain full identifiers, got %s", out)
} }
ui.OutputWriter.Reset() ui.OutputWriter.Reset()
...@@ -159,7 +159,7 @@ func TestStatusCommand_Run(t *testing.T) { ...@@ -159,7 +159,7 @@ func TestStatusCommand_Run(t *testing.T) {
t.Fatalf("expected exit 0, got: %d", code) t.Fatalf("expected exit 0, got: %d", code)
} }
out = ui.OutputWriter.String() out = ui.OutputWriter.String()
if !strings.Contains(out, evalId1) { if !strings.Contains(out, resp.EvalID) {
t.Fatalf("should contain full identifiers, got %s", out) t.Fatalf("should contain full identifiers, got %s", out)
} }
} }
......
...@@ -87,6 +87,12 @@ func (c *ValidateCommand) Run(args []string) int { ...@@ -87,6 +87,12 @@ func (c *ValidateCommand) Run(args []string) int {
return 1 return 1
} }
// Print any warnings if there are any
if jr.Warnings != "" {
c.Ui.Output(
c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", jr.Warnings)))
}
// Done! // Done!
c.Ui.Output( c.Ui.Output(
c.Colorize().Color("[bold][green]Job validation successful[reset]")) c.Colorize().Color("[bold][green]Job validation successful[reset]"))
...@@ -112,5 +118,9 @@ func (c *ValidateCommand) validateLocal(aj *api.Job) (*api.JobValidateResponse, ...@@ -112,5 +118,9 @@ func (c *ValidateCommand) validateLocal(aj *api.Job) (*api.JobValidateResponse,
} }
} }
if warnings := job.Warnings(); warnings != nil {
out.Warnings = warnings.Error()
}
return &out, nil return &out, nil
} }
...@@ -61,9 +61,12 @@ func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegis ...@@ -61,9 +61,12 @@ func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegis
// Add implicit constraints // Add implicit constraints
setImplicitConstraints(args.Job) setImplicitConstraints(args.Job)
// Validate the job. // Validate the job and capture any warnings
if err := validateJob(args.Job); err != nil { err, warnings := validateJob(args.Job)
if err != nil {
return err return err
} else if warnings != nil {
reply.Warnings = warnings.Error()
} }
if args.EnforceIndex { if args.EnforceIndex {
...@@ -289,7 +292,8 @@ func (j *Job) Summary(args *structs.JobSummaryRequest, ...@@ -289,7 +292,8 @@ func (j *Job) Summary(args *structs.JobSummaryRequest,
func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValidateResponse) error { func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValidateResponse) error {
defer metrics.MeasureSince([]string{"nomad", "job", "validate"}, time.Now()) defer metrics.MeasureSince([]string{"nomad", "job", "validate"}, time.Now())
if err := validateJob(args.Job); err != nil { err, warnings := validateJob(args.Job)
if err != nil {
if merr, ok := err.(*multierror.Error); ok { if merr, ok := err.(*multierror.Error); ok {
for _, err := range merr.Errors { for _, err := range merr.Errors {
reply.ValidationErrors = append(reply.ValidationErrors, err.Error()) reply.ValidationErrors = append(reply.ValidationErrors, err.Error())
...@@ -301,6 +305,10 @@ func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValid ...@@ -301,6 +305,10 @@ func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValid
} }
} }
if warnings != nil {
reply.Warnings = warnings.Error()
}
reply.DriverConfigValidated = true reply.DriverConfigValidated = true
return nil return nil
} }
...@@ -723,9 +731,12 @@ func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) ...@@ -723,9 +731,12 @@ func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse)
// Add implicit constraints // Add implicit constraints
setImplicitConstraints(args.Job) setImplicitConstraints(args.Job)
// Validate the job. // Validate the job and capture any warnings
if err := validateJob(args.Job); err != nil { err, warnings := validateJob(args.Job)
if err != nil {
return err return err
} else if warnings != nil {
reply.Warnings = warnings.Error()
} }
// Acquire a snapshot of the state // Acquire a snapshot of the state
...@@ -818,12 +829,15 @@ func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) ...@@ -818,12 +829,15 @@ func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse)
// validateJob validates a Job and task drivers and returns an error if there is // validateJob validates a Job and task drivers and returns an error if there is
// a validation problem or if the Job is of a type a user is not allowed to // a validation problem or if the Job is of a type a user is not allowed to
// submit. // submit.
func validateJob(job *structs.Job) error { func validateJob(job *structs.Job) (invalid, warnings error) {
validationErrors := new(multierror.Error) validationErrors := new(multierror.Error)
if err := job.Validate(); err != nil { if err := job.Validate(); err != nil {
multierror.Append(validationErrors, err) multierror.Append(validationErrors, err)
} }
// Get any warnings
warnings = job.Warnings()
// Get the signals required // Get the signals required
signals := job.RequiredSignals() signals := job.RequiredSignals()
...@@ -873,7 +887,7 @@ func validateJob(job *structs.Job) error { ...@@ -873,7 +887,7 @@ func validateJob(job *structs.Job) error {
multierror.Append(validationErrors, fmt.Errorf("job can't be submitted with a payload, only dispatched")) multierror.Append(validationErrors, fmt.Errorf("job can't be submitted with a payload, only dispatched"))
} }
return validationErrors.ErrorOrNil() return validationErrors.ErrorOrNil(), warnings
} }
// Dispatch a parameterized job. // Dispatch a parameterized job.
......
...@@ -119,6 +119,36 @@ func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) { ...@@ -119,6 +119,36 @@ func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) {
} }
} }
func TestJobEndpoint_Register_UpdateWarning(t *testing.T) {
s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0 // Prevent automatic dequeue
})
defer s1.Shutdown()
codec := rpcClient(t, s1)
testutil.WaitForLeader(t, s1.RPC)
// Create the register request with a job containing an invalid driver
// config
job := mock.Job()
job.Update.Stagger = 1 * time.Second
job.Update.MaxParallel = 1
req := &structs.JobRegisterRequest{
Job: job,
WriteRequest: structs.WriteRequest{Region: "global"},
}
// Fetch the response
var resp structs.JobRegisterResponse
err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
if err != nil {
t.Fatalf("err: %v", err)
}
if !strings.Contains(resp.Warnings, "Update stagger deprecated") {
t.Fatalf("expected a deprecation warning but got: %v", err)
}
}
func TestJobEndpoint_Register_Payload(t *testing.T) { func TestJobEndpoint_Register_Payload(t *testing.T) {
s1 := testServer(t, func(c *Config) { s1 := testServer(t, func(c *Config) {
c.NumSchedulers = 0 // Prevent automatic dequeue c.NumSchedulers = 0 // Prevent automatic dequeue
...@@ -2240,9 +2270,14 @@ func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) { ...@@ -2240,9 +2270,14 @@ func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "-> config") { err, warnings := validateJob(job)
if err == nil || !strings.Contains(err.Error(), "-> config") {
t.Fatalf("Expected config error; got %v", err) t.Fatalf("Expected config error; got %v", err)
} }
if warnings != nil {
t.Fatalf("got unexpected warnings: %v", warnings)
}
} }
func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) { func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) {
...@@ -2255,9 +2290,30 @@ func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) { ...@@ -2255,9 +2290,30 @@ func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) {
ChangeSignal: "SIGUSR1", ChangeSignal: "SIGUSR1",
} }
if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "support sending signals") { err, warnings := validateJob(job)
if err == nil || !strings.Contains(err.Error(), "support sending signals") {
t.Fatalf("Expected signal feasibility error; got %v", err) t.Fatalf("Expected signal feasibility error; got %v", err)
} }
if warnings != nil {
t.Fatalf("got unexpected warnings: %v", warnings)
}
}
func TestJobEndpoint_ValidateJob_UpdateWarning(t *testing.T) {
// Create a mock job with an invalid config
job := mock.Job()
job.Update.Stagger = 1 * time.Second
job.Update.MaxParallel = 1
err, warnings := validateJob(job)
if err != nil {
t.Fatalf("Unexpected validation error; got %v", err)
}
if !strings.Contains(warnings.Error(), "Update stagger deprecated") {
t.Fatalf("expected a deprecation warning but got: %v", err)
}
} }
func TestJobEndpoint_Dispatch(t *testing.T) { func TestJobEndpoint_Dispatch(t *testing.T) {
......
...@@ -484,6 +484,11 @@ type JobRegisterResponse struct { ...@@ -484,6 +484,11 @@ type JobRegisterResponse struct {
EvalID string EvalID string
EvalCreateIndex uint64 EvalCreateIndex uint64
JobModifyIndex uint64 JobModifyIndex uint64
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
QueryMeta QueryMeta
} }
...@@ -506,6 +511,10 @@ type JobValidateResponse struct { ...@@ -506,6 +511,10 @@ type JobValidateResponse struct {
// Error is a string version of any error that may have occured // Error is a string version of any error that may have occured
Error string Error string
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
} }
// NodeUpdateResponse is used to respond to a node update // NodeUpdateResponse is used to respond to a node update
...@@ -620,6 +629,10 @@ type JobPlanResponse struct { ...@@ -620,6 +629,10 @@ type JobPlanResponse struct {
// submitted. // submitted.
NextPeriodicLaunch time.Time NextPeriodicLaunch time.Time
// Warnings contains any warnings about the given job. These may include
// deprecation warnings.
Warnings string
WriteMeta WriteMeta
} }
...@@ -1357,6 +1370,18 @@ func (j *Job) Validate() error { ...@@ -1357,6 +1370,18 @@ func (j *Job) Validate() error {
return mErr.ErrorOrNil() return mErr.ErrorOrNil()
} }
// Warnings returns a list of warnings that may be from dubious settings or
// deprecation warnings.
func (j *Job) Warnings() error {
var mErr multierror.Error
if j.Update.Stagger > 0 {
multierror.Append(&mErr, fmt.Errorf("Update stagger deprecated. A best effort conversion to new syntax will be applied. Please update upgrade stanza before v0.7.0"))
}
return mErr.ErrorOrNil()
}
// LookupTaskGroup finds a task group by name // LookupTaskGroup finds a task group by name
func (j *Job) LookupTaskGroup(name string) *TaskGroup { func (j *Job) LookupTaskGroup(name string) *TaskGroup {
for _, tg := range j.TaskGroups { for _, tg := range j.TaskGroups {
......
...@@ -95,6 +95,45 @@ func TestJob_Validate(t *testing.T) { ...@@ -95,6 +95,45 @@ func TestJob_Validate(t *testing.T) {
} }
} }
func TestJob_Warnings(t *testing.T) {
cases := []struct {
Name string
Job *Job
Expected []string
}{
{
Name: "Old Update spec",
Job: &Job{
Update: UpdateStrategy{
MaxParallel: 2,
Stagger: 10 * time.Second,
},
},
Expected: []string{"Update stagger deprecated"},
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
warnings := c.Job.Warnings()
if warnings == nil {
if len(c.Expected) == 0 {
return
} else {
t.Fatal("Got no warnings when they were expected")
}
}
a := warnings.Error()
for _, e := range c.Expected {
if !strings.Contains(a, e) {
t.Fatalf("Got warnings %q; didn't contain %q", a, e)
}
}
})
}
}
func testJob() *Job { func testJob() *Job {
return &Job{ return &Job{
Region: "global", Region: "global",
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment