diff --git a/.changelog/19652.txt b/.changelog/19652.txt new file mode 100644 index 000000000..10dfce052 --- /dev/null +++ b/.changelog/19652.txt @@ -0,0 +1,3 @@ +```release-note:bug +server: Fix panic when validating non-service reschedule block +``` diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 8d6613605..334698c59 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -4674,7 +4674,7 @@ func (j *Job) Validate() error { } if tg.MaxClientDisconnect != nil && - tg.ReschedulePolicy.Attempts > 0 && + (tg.ReschedulePolicy != nil && tg.ReschedulePolicy.Attempts > 0) && tg.PreventRescheduleOnLost { err := fmt.Errorf("max_client_disconnect and prevent_reschedule_on_lost cannot be enabled when rechedule.attempts > 0") mErr.Errors = append(mErr.Errors, err) diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 5372ee5fe..0961f845b 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -470,6 +470,39 @@ func TestJob_ValidateNullChar(t *testing.T) { assert.Error(job.Validate(), "null character in task name should not validate") } +func TestJob_Validate_DisconnectRescheduleLost(t *testing.T) { + ci.Parallel(t) + + // Craft our speciality jobspec to test this particular use-case. + testDisconnectRescheduleLostJob := &Job{ + ID: "gh19644", + Name: "gh19644", + Region: "global", + Type: JobTypeSystem, + TaskGroups: []*TaskGroup{ + { + Name: "cache", + MaxClientDisconnect: pointer.Of(1 * time.Hour), + PreventRescheduleOnLost: true, + Tasks: []*Task{ + { + Name: "redis", + Driver: "docker", + Config: map[string]interface{}{ + "image": "redis:7", + }, + LogConfig: DefaultLogConfig(), + }, + }, + }, + }, + } + + testDisconnectRescheduleLostJob.Canonicalize() + + must.NoError(t, testDisconnectRescheduleLostJob.Validate()) +} + func TestJob_Warnings(t *testing.T) { ci.Parallel(t)