Nest restart fields in CheckRestart

This commit is contained in:
Michael Schurter 2017-08-24 17:18:06 -07:00
parent bf34505509
commit b35d208428
3 changed files with 154 additions and 56 deletions

View file

@ -79,36 +79,43 @@ func (r *RestartPolicy) Merge(rp *RestartPolicy) {
} }
} }
// CheckRestart describes if and when a task should be restarted based on
// failing health checks.
type CheckRestart struct {
Limit int `mapstructure:"limit"`
Grace time.Duration `mapstructure:"grace_period"`
OnWarning bool `mapstructure:"on_warning"`
}
// The ServiceCheck data model represents the consul health check that // The ServiceCheck data model represents the consul health check that
// Nomad registers for a Task // Nomad registers for a Task
type ServiceCheck struct { type ServiceCheck struct {
Id string Id string
Name string Name string
Type string Type string
Command string Command string
Args []string Args []string
Path string Path string
Protocol string Protocol string
PortLabel string `mapstructure:"port"` PortLabel string `mapstructure:"port"`
Interval time.Duration Interval time.Duration
Timeout time.Duration Timeout time.Duration
InitialStatus string `mapstructure:"initial_status"` InitialStatus string `mapstructure:"initial_status"`
TLSSkipVerify bool `mapstructure:"tls_skip_verify"` TLSSkipVerify bool `mapstructure:"tls_skip_verify"`
Header map[string][]string Header map[string][]string
Method string Method string
RestartAfter int CheckRestart *CheckRestart `mapstructure:"check_restart"`
RestartGrace time.Duration
RestartWarning bool
} }
// The Service model represents a Consul service definition // The Service model represents a Consul service definition
type Service struct { type Service struct {
Id string Id string
Name string Name string
Tags []string Tags []string
PortLabel string `mapstructure:"port"` PortLabel string `mapstructure:"port"`
AddressMode string `mapstructure:"address_mode"` AddressMode string `mapstructure:"address_mode"`
Checks []ServiceCheck Checks []ServiceCheck
CheckRestart *CheckRestart `mapstructure:"check_restart"`
} }
func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) { func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) {

View file

@ -685,27 +685,38 @@ func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
Tags: service.Tags, Tags: service.Tags,
AddressMode: service.AddressMode, AddressMode: service.AddressMode,
} }
if service.CheckRestart != nil {
structsTask.Services[i].CheckRestart = &structs.CheckRestart{
Limit: service.CheckRestart.Limit,
Grace: service.CheckRestart.Grace,
OnWarning: service.CheckRestart.OnWarning,
}
}
if l := len(service.Checks); l != 0 { if l := len(service.Checks); l != 0 {
structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l) structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
for j, check := range service.Checks { for j, check := range service.Checks {
structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
Name: check.Name, Name: check.Name,
Type: check.Type, Type: check.Type,
Command: check.Command, Command: check.Command,
Args: check.Args, Args: check.Args,
Path: check.Path, Path: check.Path,
Protocol: check.Protocol, Protocol: check.Protocol,
PortLabel: check.PortLabel, PortLabel: check.PortLabel,
Interval: check.Interval, Interval: check.Interval,
Timeout: check.Timeout, Timeout: check.Timeout,
InitialStatus: check.InitialStatus, InitialStatus: check.InitialStatus,
TLSSkipVerify: check.TLSSkipVerify, TLSSkipVerify: check.TLSSkipVerify,
Header: check.Header, Header: check.Header,
Method: check.Method, Method: check.Method,
RestartAfter: check.RestartAfter, }
RestartGrace: check.RestartGrace, if check.CheckRestart != nil {
RestartWarning: check.RestartWarning, structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
Limit: check.CheckRestart.Limit,
Grace: check.CheckRestart.Grace,
OnWarning: check.CheckRestart.OnWarning,
}
} }
} }
} }

View file

@ -2757,6 +2757,70 @@ func (tg *TaskGroup) GoString() string {
return fmt.Sprintf("*%#v", *tg) return fmt.Sprintf("*%#v", *tg)
} }
// CheckRestart describes if and when a task should be restarted based on
// failing health checks.
type CheckRestart struct {
Limit int // Restart task after this many unhealthy intervals
Grace time.Duration // Grace time to give tasks after starting to get healthy
OnWarning bool // If true treat checks in `warning` as unhealthy
}
func (c *CheckRestart) Copy() *CheckRestart {
if c == nil {
return nil
}
nc := new(CheckRestart)
*nc = *c
return nc
}
// Merge non-zero values from other CheckRestart into a copy of this
// CheckRestart. Returns nil iff both are nil.
func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart {
if c == nil {
// Just return other
return o
}
nc := c.Copy()
if o == nil {
// Nothing to merge
return nc.Copy()
}
if nc.Limit == 0 {
nc.Limit = o.Limit
}
if nc.Grace == 0 {
nc.Grace = o.Grace
}
if !nc.OnWarning {
nc.OnWarning = o.OnWarning
}
return nc
}
func (c *CheckRestart) Validate() error {
if c == nil {
return nil
}
if c.Limit < 0 {
return fmt.Errorf("limit must be greater than or equal to 0 but found %d", c.Limit)
}
if c.Grace < 0 {
return fmt.Errorf("grace period must be greater than or equal to 0 but found %d", c.Grace)
}
return nil
}
const ( const (
ServiceCheckHTTP = "http" ServiceCheckHTTP = "http"
ServiceCheckTCP = "tcp" ServiceCheckTCP = "tcp"
@ -2775,22 +2839,20 @@ const (
// The ServiceCheck data model represents the consul health check that // The ServiceCheck data model represents the consul health check that
// Nomad registers for a Task // Nomad registers for a Task
type ServiceCheck struct { type ServiceCheck struct {
Name string // Name of the check, defaults to id Name string // Name of the check, defaults to id
Type string // Type of the check - tcp, http, docker and script Type string // Type of the check - tcp, http, docker and script
Command string // Command is the command to run for script checks Command string // Command is the command to run for script checks
Args []string // Args is a list of argumes 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 Path string // path of the health check url for http type check
Protocol string // Protocol to use if check is http, defaults to http Protocol string // Protocol to use if check is http, defaults to http
PortLabel string // The port to use for tcp/http checks PortLabel string // The port to use for tcp/http checks
Interval time.Duration // Interval of the check Interval time.Duration // Interval of the check
Timeout time.Duration // Timeout of the response from the check before consul fails 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 // Initial status of the check
TLSSkipVerify bool // Skip TLS verification when Protocol=https TLSSkipVerify bool // Skip TLS verification when Protocol=https
Method string // HTTP Method to use (GET by default) Method string // HTTP Method to use (GET by default)
Header map[string][]string // HTTP Headers for Consul to set when making HTTP checks Header map[string][]string // HTTP Headers for Consul to set when making HTTP checks
RestartAfter int // Restart task after this many unhealthy intervals CheckRestart *CheckRestart // If and when a task should be restarted based on checks
RestartGrace time.Duration // Grace time to give tasks after starting to get healthy
RestartWarning bool // If true treat checks in `warning` as unhealthy
} }
func (sc *ServiceCheck) Copy() *ServiceCheck { func (sc *ServiceCheck) Copy() *ServiceCheck {
@ -2801,6 +2863,7 @@ func (sc *ServiceCheck) Copy() *ServiceCheck {
*nsc = *sc *nsc = *sc
nsc.Args = helper.CopySliceString(sc.Args) nsc.Args = helper.CopySliceString(sc.Args)
nsc.Header = helper.CopyMapStringSliceString(sc.Header) nsc.Header = helper.CopyMapStringSliceString(sc.Header)
nsc.CheckRestart = sc.CheckRestart.Copy()
return nsc return nsc
} }
@ -2866,7 +2929,7 @@ func (sc *ServiceCheck) validate() error {
} }
return nil return sc.CheckRestart.Validate()
} }
// RequiresPort returns whether the service check requires the task has a port. // RequiresPort returns whether the service check requires the task has a port.
@ -2939,6 +3002,9 @@ type Service struct {
Tags []string // List of tags for the service Tags []string // List of tags for the service
Checks []*ServiceCheck // List of checks associated with the service Checks []*ServiceCheck // List of checks associated with the service
// CheckRestart will be propagated to Checks if set.
CheckRestart *CheckRestart
} }
func (s *Service) Copy() *Service { func (s *Service) Copy() *Service {
@ -2948,6 +3014,7 @@ func (s *Service) Copy() *Service {
ns := new(Service) ns := new(Service)
*ns = *s *ns = *s
ns.Tags = helper.CopySliceString(ns.Tags) ns.Tags = helper.CopySliceString(ns.Tags)
ns.CheckRestart = s.CheckRestart.Copy()
if s.Checks != nil { if s.Checks != nil {
checks := make([]*ServiceCheck, len(ns.Checks)) checks := make([]*ServiceCheck, len(ns.Checks))
@ -2983,6 +3050,14 @@ func (s *Service) Canonicalize(job string, taskGroup string, task string) {
for _, check := range s.Checks { for _, check := range s.Checks {
check.Canonicalize(s.Name) check.Canonicalize(s.Name)
} }
// If CheckRestart is set propagate it to checks
if s.CheckRestart != nil {
for _, check := range s.Checks {
// Merge Service CheckRestart into Check's so Check's takes precedence
check.CheckRestart = check.CheckRestart.Merge(s.CheckRestart)
}
}
} }
// Validate checks if the Check definition is valid // Validate checks if the Check definition is valid
@ -3016,6 +3091,11 @@ func (s *Service) Validate() error {
mErr.Errors = append(mErr.Errors, fmt.Errorf("check %s invalid: %v", c.Name, err)) mErr.Errors = append(mErr.Errors, fmt.Errorf("check %s invalid: %v", c.Name, err))
} }
} }
if s.CheckRestart != nil && len(s.Checks) == 0 {
mErr.Errors = append(mErr.Errors, fmt.Errorf("check_restart specified but no checks"))
}
return mErr.ErrorOrNil() return mErr.ErrorOrNil()
} }