From 3ba8faeae37210f4a6d2f5380134f72532c05697 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Fri, 10 Feb 2017 16:57:47 -0800 Subject: [PATCH] Add leader task to api and server side --- api/tasks.go | 1 + jobspec/parse.go | 1 + jobspec/parse_test.go | 1 + jobspec/test-fixtures/basic.hcl | 1 + nomad/structs/diff.go | 2 +- nomad/structs/diff_test.go | 10 ++++++++++ nomad/structs/structs.go | 15 ++++++++++++++- nomad/structs/structs_test.go | 9 ++++++--- 8 files changed, 35 insertions(+), 5 deletions(-) diff --git a/api/tasks.go b/api/tasks.go index b22938e1e..95cec1b0a 100644 --- a/api/tasks.go +++ b/api/tasks.go @@ -163,6 +163,7 @@ type Task struct { Vault *Vault Templates []*Template DispatchPayload *DispatchPayloadConfig + Leader bool } // TaskArtifact is used to download artifacts before running a task. diff --git a/jobspec/parse.go b/jobspec/parse.go index e109854c4..628e17787 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -568,6 +568,7 @@ func parseTasks(jobName string, taskGroupName string, result *[]*structs.Task, l "driver", "env", "kill_timeout", + "leader", "logs", "meta", "resources", diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index 1e3485b69..068618d96 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -181,6 +181,7 @@ func TestParse(t *testing.T) { Perms: "777", }, }, + Leader: true, }, &structs.Task{ Name: "storagelocker", diff --git a/jobspec/test-fixtures/basic.hcl b/jobspec/test-fixtures/basic.hcl index b70ad03dd..72870620f 100644 --- a/jobspec/test-fixtures/basic.hcl +++ b/jobspec/test-fixtures/basic.hcl @@ -50,6 +50,7 @@ job "binstore-storagelocker" { task "binstore" { driver = "docker" user = "bob" + leader = true config { image = "hashicorp/binstore" diff --git a/nomad/structs/diff.go b/nomad/structs/diff.go index f4ec05e69..2e3751cfe 100644 --- a/nomad/structs/diff.go +++ b/nomad/structs/diff.go @@ -403,7 +403,7 @@ func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) { diff.Objects = append(diff.Objects, vDiff) } - // Artifacts diff + // Template diff tmplDiffs := primitiveObjectSetDiff( interfaceSlice(t.Templates), interfaceSlice(other.Templates), diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 852e91db5..a9f88968f 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -1880,6 +1880,7 @@ func TestTaskDiff(t *testing.T) { "foo": "bar", }, KillTimeout: 1 * time.Second, + Leader: true, }, New: &Task{ Name: "foo", @@ -1892,6 +1893,7 @@ func TestTaskDiff(t *testing.T) { "foo": "bar", }, KillTimeout: 1 * time.Second, + Leader: true, }, Expected: &TaskDiff{ Type: DiffTypeNone, @@ -1911,6 +1913,7 @@ func TestTaskDiff(t *testing.T) { "foo": "bar", }, KillTimeout: 1 * time.Second, + Leader: true, }, New: &Task{ Name: "foo", @@ -1923,6 +1926,7 @@ func TestTaskDiff(t *testing.T) { "foo": "baz", }, KillTimeout: 2 * time.Second, + Leader: false, }, Expected: &TaskDiff{ Type: DiffTypeEdited, @@ -1946,6 +1950,12 @@ func TestTaskDiff(t *testing.T) { Old: "1000000000", New: "2000000000", }, + { + Type: DiffTypeEdited, + Name: "Leader", + Old: "true", + New: "false", + }, { Type: DiffTypeEdited, Name: "Meta[foo]", diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 3fe27853e..90b3ab662 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1931,8 +1931,9 @@ func (tg *TaskGroup) Validate() error { mErr.Errors = append(mErr.Errors, fmt.Errorf("Task Group %v should have an ephemeral disk object", tg.Name)) } - // Check for duplicate tasks + // Check for duplicate tasks and that there is only leader task if any tasks := make(map[string]int) + leaderTasks := 0 for idx, task := range tg.Tasks { if task.Name == "" { mErr.Errors = append(mErr.Errors, fmt.Errorf("Task %d missing name", idx+1)) @@ -1941,6 +1942,14 @@ func (tg *TaskGroup) Validate() error { } else { tasks[task.Name] = idx } + + if task.Leader { + leaderTasks++ + } + } + + if leaderTasks > 1 { + mErr.Errors = append(mErr.Errors, fmt.Errorf("Only one task may be marked as leader")) } // Validate the tasks @@ -2289,6 +2298,10 @@ type Task struct { // Artifacts is a list of artifacts to download and extract before running // the task. Artifacts []*TaskArtifact + + // Leader marks the task as the leader within the group. When the leader + // task exits, other tasks will be gracefully terminated. + Leader bool } func (t *Task) Copy() *Task { diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 610a00044..58555b485 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -419,8 +419,8 @@ func TestTaskGroup_Validate(t *testing.T) { Name: "web", Count: 1, Tasks: []*Task{ - &Task{Name: "web"}, - &Task{Name: "web"}, + &Task{Name: "web", Leader: true}, + &Task{Name: "web", Leader: true}, &Task{}, }, RestartPolicy: &RestartPolicy{ @@ -442,7 +442,10 @@ func TestTaskGroup_Validate(t *testing.T) { if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") { t.Fatalf("err: %s", err) } - if !strings.Contains(mErr.Errors[3].Error(), "Task web validation failed") { + if !strings.Contains(mErr.Errors[3].Error(), "Only one task may be marked as leader") { + t.Fatalf("err: %s", err) + } + if !strings.Contains(mErr.Errors[4].Error(), "Task web validation failed") { t.Fatalf("err: %s", err) } }