From fbb9108ed57b5daf9acdd45f1da1a2a1790fa7ed Mon Sep 17 00:00:00 2001 From: Marin Date: Tue, 16 Aug 2016 12:05:15 -0700 Subject: [PATCH 1/2] Add support for initial check status --- command/agent/consul/syncer.go | 1 + command/agent/consul/syncer_test.go | 13 ++++-- jobspec/parse.go | 1 + jobspec/parse_test.go | 14 ++++++ .../service-check-initial-status.hcl | 22 ++++++++++ nomad/structs/structs.go | 31 +++++++++---- nomad/structs/structs_test.go | 44 +++++++++++++++++++ 7 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 jobspec/test-fixtures/service-check-initial-status.hcl diff --git a/command/agent/consul/syncer.go b/command/agent/consul/syncer.go index 525b0aba8afa..736e15c8e0c9 100644 --- a/command/agent/consul/syncer.go +++ b/command/agent/consul/syncer.go @@ -720,6 +720,7 @@ func (c *Syncer) createCheckReg(check *structs.ServiceCheck, serviceReg *consul. default: return nil, fmt.Errorf("check type %+q not valid", check.Type) } + chkReg.Status = check.InitialStatus return &chkReg, nil } diff --git a/command/agent/consul/syncer_test.go b/command/agent/consul/syncer_test.go index 7e888c69ef12..938770b370dc 100644 --- a/command/agent/consul/syncer_test.go +++ b/command/agent/consul/syncer_test.go @@ -21,10 +21,11 @@ const ( var ( logger = log.New(os.Stdout, "", log.LstdFlags) check1 = structs.ServiceCheck{ - Name: "check-foo-1", - Type: structs.ServiceCheckTCP, - Interval: 30 * time.Second, - Timeout: 5 * time.Second, + Name: "check-foo-1", + Type: structs.ServiceCheckTCP, + Interval: 30 * time.Second, + Timeout: 5 * time.Second, + InitialStatus: "passing", } check2 = structs.ServiceCheck{ Name: "check1", @@ -86,6 +87,10 @@ func TestCheckRegistration(t *testing.T) { t.Fatalf("expected: %v, actual: %v", expected, check3Reg.HTTP) } + expected = "passing" + if check1Reg.Status != expected { + t.Fatalf("expected: %v, actual: %v", expected, check1Reg.Status) + } } func TestConsulServiceRegisterServices(t *testing.T) { diff --git a/jobspec/parse.go b/jobspec/parse.go index 81639d4cb9ae..26ede1faf4cd 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -770,6 +770,7 @@ func parseChecks(service *structs.Service, checkObjs *ast.ObjectList) error { "port", "command", "args", + "initial_status", } if err := checkHCLKeys(co.Val, valid); err != nil { return multierror.Prefix(err, "check ->") diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index 80ca8c4ed5cb..a83584dacd75 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -525,3 +525,17 @@ func TestIncorrectKey(t *testing.T) { t.Fatalf("Expected collision error; got %v", err) } } + +func TestServiceCheckInitialStatus(t *testing.T) { + + path, err := filepath.Abs(filepath.Join("./test-fixtures", "service-check-initial-status.hcl")) + if err != nil { + t.Fatalf("Can't get absolute path for file: %s", err) + } + + _, err = ParseFile(path) + + if err != nil { + t.Fatalf("Expected no error; got %v", err) + } +} diff --git a/jobspec/test-fixtures/service-check-initial-status.hcl b/jobspec/test-fixtures/service-check-initial-status.hcl new file mode 100644 index 000000000000..3a328f349d03 --- /dev/null +++ b/jobspec/test-fixtures/service-check-initial-status.hcl @@ -0,0 +1,22 @@ +job "foo" { + + group "group" { + count = 1 + + task "task" { + + service { + tags = ["foo", "bar"] + + check { + name = "check-name" + type = "http" + interval = "10s" + timeout = "2s" + initial_status = "passing" + } + } + } + } +} + diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index f745daf92e2f..3fe7ae499b5d 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -18,6 +18,7 @@ import ( "time" "github.com/gorhill/cronexpr" + "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-version" "github.com/hashicorp/nomad/helper/args" @@ -1591,15 +1592,16 @@ const ( // The ServiceCheck data model represents the consul health check that // Nomad registers for a Task type ServiceCheck struct { - Name string // Name of the check, defaults to id - Type string // Type of the check - tcp, http, docker and script - Command string // Command is the command to run for script checks - Args []string // Args is a list of argumes for script checks - Path string // path of the health check url for http type check - Protocol string // Protocol to use if check is http, defaults to http - PortLabel string `mapstructure:"port"` // The port to use for tcp/http checks - Interval time.Duration // Interval of the check - Timeout time.Duration // Timeout of the response from the check before consul fails the check + Name string // Name of the check, defaults to id + Type string // Type of the check - tcp, http, docker and script + Command string // Command is the command to run for script checks + Args []string // Args is a list of argumes for script checks + Path string // path of the health check url for http type check + Protocol string // Protocol to use if check is http, defaults to http + PortLabel string `mapstructure:"port"` // The port to use for tcp/http checks + Interval time.Duration // Interval of the check + Timeout time.Duration // Timeout of the response from the check before consul fails the check + InitialStatus string // Initial status of the check } func (sc *ServiceCheck) Copy() *ServiceCheck { @@ -1653,6 +1655,17 @@ func (sc *ServiceCheck) validate() error { return fmt.Errorf("interval (%v) can not be lower than %v", sc.Interval, minCheckInterval) } + switch sc.InitialStatus { + case "": + case api.HealthUnknown: + case api.HealthPassing: + case api.HealthWarning: + case api.HealthCritical: + default: + return fmt.Errorf(`invalid initial check state (%s), must be one of "%s", "%s", "%s", or "%s"`, sc.InitialStatus, api.HealthUnknown, api.HealthPassing, api.HealthWarning, api.HealthCritical) + + } + return nil } diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 619702514c3c..45fb039a6074 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" ) @@ -364,6 +365,49 @@ func TestTask_Validate_Services(t *testing.T) { } } +func TestTask_Validate_Service_Check(t *testing.T) { + + check1 := ServiceCheck{ + Name: "check-name", + Type: ServiceCheckTCP, + Interval: 10 * time.Second, + Timeout: 2 * time.Second, + } + + err := check1.validate() + if err != nil { + t.Fatal("err: %v", err) + } + + check1.InitialStatus = "foo" + err = check1.validate() + if err == nil { + t.Fatal("Expected an error") + } + + if !strings.Contains(err.Error(), "invalid initial check state (foo)") { + t.Fatalf("err: %v", err) + } + + check1.InitialStatus = api.HealthCritical + err = check1.validate() + if err != nil { + t.Fatalf("err: %v", err) + } + + check1.InitialStatus = api.HealthPassing + err = check1.validate() + if err != nil { + t.Fatalf("err: %v", err) + } + + check1.InitialStatus = "" + err = check1.validate() + if err != nil { + t.Fatalf("err: %v", err) + } +} + func TestTask_Validate_LogConfig(t *testing.T) { task := &Task{ LogConfig: DefaultLogConfig(), From 56b35b9a959b8f4af9b66454500fc35ffebad695 Mon Sep 17 00:00:00 2001 From: Marin Date: Tue, 16 Aug 2016 14:34:36 -0700 Subject: [PATCH 2/2] fix initial status tests --- jobspec/parse_test.go | 53 ++++++++++++++----- .../service-check-initial-status.hcl | 5 +- nomad/structs/structs.go | 4 +- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index a83584dacd75..1e0657917a7e 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -384,6 +384,45 @@ func TestParse(t *testing.T) { }, false, }, + { + "service-check-initial-status.hcl", + &structs.Job{ + ID: "check_initial_status", + Name: "check_initial_status", + Type: "service", + Priority: 50, + Region: "global", + TaskGroups: []*structs.TaskGroup{ + &structs.TaskGroup{ + Name: "group", + Count: 1, + Tasks: []*structs.Task{ + &structs.Task{ + Name: "task", + Services: []*structs.Service{ + { + Name: "check_initial_status-group-task", + Tags: []string{"foo", "bar"}, + PortLabel: "http", + Checks: []*structs.ServiceCheck{ + { + Name: "check-name", + Type: "http", + Interval: 10 * time.Second, + Timeout: 2 * time.Second, + InitialStatus: "passing", + }, + }, + }, + }, + LogConfig: structs.DefaultLogConfig(), + }, + }, + }, + }, + }, + false, + }, } for _, tc := range cases { @@ -525,17 +564,3 @@ func TestIncorrectKey(t *testing.T) { t.Fatalf("Expected collision error; got %v", err) } } - -func TestServiceCheckInitialStatus(t *testing.T) { - - path, err := filepath.Abs(filepath.Join("./test-fixtures", "service-check-initial-status.hcl")) - if err != nil { - t.Fatalf("Can't get absolute path for file: %s", err) - } - - _, err = ParseFile(path) - - if err != nil { - t.Fatalf("Expected no error; got %v", err) - } -} diff --git a/jobspec/test-fixtures/service-check-initial-status.hcl b/jobspec/test-fixtures/service-check-initial-status.hcl index 3a328f349d03..d617da5ecd95 100644 --- a/jobspec/test-fixtures/service-check-initial-status.hcl +++ b/jobspec/test-fixtures/service-check-initial-status.hcl @@ -1,12 +1,13 @@ -job "foo" { +job "check_initial_status" { + type = "service" group "group" { count = 1 task "task" { - service { tags = ["foo", "bar"] + port = "http" check { name = "check-name" diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 3fe7ae499b5d..aa46c2e7ef67 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1601,7 +1601,7 @@ type ServiceCheck struct { PortLabel string `mapstructure:"port"` // The port to use for tcp/http checks Interval time.Duration // Interval of the check Timeout time.Duration // Timeout of the response from the check before consul fails the check - InitialStatus string // Initial status of the check + InitialStatus string `mapstructure:"initial_status"` // Initial status of the check } func (sc *ServiceCheck) Copy() *ServiceCheck { @@ -1662,7 +1662,7 @@ func (sc *ServiceCheck) validate() error { case api.HealthWarning: case api.HealthCritical: default: - return fmt.Errorf(`invalid initial check state (%s), must be one of "%s", "%s", "%s", or "%s"`, sc.InitialStatus, api.HealthUnknown, api.HealthPassing, api.HealthWarning, api.HealthCritical) + return fmt.Errorf(`invalid initial check state (%s), must be one of %q, %q, %q, %q or empty`, sc.InitialStatus, api.HealthUnknown, api.HealthPassing, api.HealthWarning, api.HealthCritical) }