open-nomad/api/services.go

555 lines
13 KiB
Go

package api
import (
"fmt"
"time"
)
// 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"`
IgnoreWarnings bool `mapstructure:"ignore_warnings"`
}
// Canonicalize CheckRestart fields if not nil.
func (c *CheckRestart) Canonicalize() {
if c == nil {
return
}
if c.Grace == nil {
c.Grace = timeToPtr(1 * time.Second)
}
}
// Copy returns a copy of CheckRestart or nil if unset.
func (c *CheckRestart) Copy() *CheckRestart {
if c == nil {
return nil
}
nc := new(CheckRestart)
nc.Limit = c.Limit
if c.Grace != nil {
g := *c.Grace
nc.Grace = &g
}
nc.IgnoreWarnings = c.IgnoreWarnings
return nc
}
// Merge values from other CheckRestart over default values on this
// CheckRestart and return merged copy.
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
}
if o.Limit > 0 {
nc.Limit = o.Limit
}
if o.Grace != nil {
nc.Grace = o.Grace
}
if o.IgnoreWarnings {
nc.IgnoreWarnings = o.IgnoreWarnings
}
return nc
}
// ServiceCheck represents the consul health check that Nomad registers.
type ServiceCheck struct {
//FIXME Id is unused. Remove?
Id string
Name string
Type string
Command string
Args []string
Path string
Protocol string
PortLabel string `mapstructure:"port"`
Expose bool
AddressMode string `mapstructure:"address_mode"`
Interval time.Duration
Timeout time.Duration
InitialStatus string `mapstructure:"initial_status"`
TLSSkipVerify bool `mapstructure:"tls_skip_verify"`
Header map[string][]string
Method string
CheckRestart *CheckRestart `mapstructure:"check_restart"`
GRPCService string `mapstructure:"grpc_service"`
GRPCUseTLS bool `mapstructure:"grpc_use_tls"`
TaskName string `mapstructure:"task"`
SuccessBeforePassing int `mapstructure:"success_before_passing"`
FailuresBeforeCritical int `mapstructure:"failures_before_critical"`
}
// Service represents a Consul service definition.
type Service struct {
//FIXME Id is unused. Remove?
Id string
Name string
Tags []string
CanaryTags []string `mapstructure:"canary_tags"`
EnableTagOverride bool `mapstructure:"enable_tag_override"`
PortLabel string `mapstructure:"port"`
AddressMode string `mapstructure:"address_mode"`
Checks []ServiceCheck
CheckRestart *CheckRestart `mapstructure:"check_restart"`
Connect *ConsulConnect
Meta map[string]string
CanaryMeta map[string]string
TaskName string `mapstructure:"task"`
}
// Canonicalize the Service by ensuring its name and address mode are set. Task
// will be nil for group services.
func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) {
if s.Name == "" {
if t != nil {
s.Name = fmt.Sprintf("%s-%s-%s", *job.Name, *tg.Name, t.Name)
} else {
s.Name = fmt.Sprintf("%s-%s", *job.Name, *tg.Name)
}
}
// Default to AddressModeAuto
if s.AddressMode == "" {
s.AddressMode = "auto"
}
s.Connect.Canonicalize()
// Canonicalize CheckRestart on Checks and merge Service.CheckRestart
// into each check.
for i, check := range s.Checks {
s.Checks[i].CheckRestart = s.CheckRestart.Merge(check.CheckRestart)
s.Checks[i].CheckRestart.Canonicalize()
if s.Checks[i].SuccessBeforePassing < 0 {
s.Checks[i].SuccessBeforePassing = 0
}
if s.Checks[i].FailuresBeforeCritical < 0 {
s.Checks[i].FailuresBeforeCritical = 0
}
}
}
// ConsulConnect represents a Consul Connect jobspec stanza.
type ConsulConnect struct {
Native bool
Gateway *ConsulGateway
SidecarService *ConsulSidecarService `mapstructure:"sidecar_service"`
SidecarTask *SidecarTask `mapstructure:"sidecar_task"`
}
func (cc *ConsulConnect) Canonicalize() {
if cc == nil {
return
}
cc.SidecarService.Canonicalize()
cc.SidecarTask.Canonicalize()
cc.Gateway.Canonicalize()
}
// ConsulSidecarService represents a Consul Connect SidecarService jobspec
// stanza.
type ConsulSidecarService struct {
Tags []string
Port string
Proxy *ConsulProxy
}
func (css *ConsulSidecarService) Canonicalize() {
if css == nil {
return
}
if len(css.Tags) == 0 {
css.Tags = nil
}
css.Proxy.Canonicalize()
}
// SidecarTask represents a subset of Task fields that can be set to override
// the fields of the Task generated for the sidecar
type SidecarTask struct {
Name string
Driver string
User string
Config map[string]interface{}
Env map[string]string
Resources *Resources
Meta map[string]string
KillTimeout *time.Duration `mapstructure:"kill_timeout"`
LogConfig *LogConfig `mapstructure:"logs"`
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay"`
KillSignal string `mapstructure:"kill_signal"`
}
func (st *SidecarTask) Canonicalize() {
if st == nil {
return
}
if len(st.Config) == 0 {
st.Config = nil
}
if len(st.Env) == 0 {
st.Env = nil
}
if st.Resources == nil {
st.Resources = DefaultResources()
} else {
st.Resources.Canonicalize()
}
if st.LogConfig == nil {
st.LogConfig = DefaultLogConfig()
} else {
st.LogConfig.Canonicalize()
}
if len(st.Meta) == 0 {
st.Meta = nil
}
if st.KillTimeout == nil {
st.KillTimeout = timeToPtr(5 * time.Second)
}
if st.ShutdownDelay == nil {
st.ShutdownDelay = timeToPtr(0)
}
}
// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
type ConsulProxy struct {
LocalServiceAddress string `mapstructure:"local_service_address"`
LocalServicePort int `mapstructure:"local_service_port"`
ExposeConfig *ConsulExposeConfig `mapstructure:"expose"`
Upstreams []*ConsulUpstream
Config map[string]interface{}
}
func (cp *ConsulProxy) Canonicalize() {
if cp == nil {
return
}
cp.ExposeConfig.Canonicalize()
if len(cp.Upstreams) == 0 {
cp.Upstreams = nil
}
if len(cp.Config) == 0 {
cp.Config = nil
}
}
// ConsulUpstream represents a Consul Connect upstream jobspec stanza.
type ConsulUpstream struct {
DestinationName string `mapstructure:"destination_name"`
LocalBindPort int `mapstructure:"local_bind_port"`
}
type ConsulExposeConfig struct {
Path []*ConsulExposePath `mapstructure:"path"`
}
func (cec *ConsulExposeConfig) Canonicalize() {
if cec == nil {
return
}
if len(cec.Path) == 0 {
cec.Path = nil
}
}
type ConsulExposePath struct {
Path string
Protocol string
LocalPathPort int `mapstructure:"local_path_port"`
ListenerPort string `mapstructure:"listener_port"`
}
// ConsulGateway is used to configure one of the Consul Connect Gateway types.
type ConsulGateway struct {
// Proxy is used to configure the Envoy instance acting as the gateway.
Proxy *ConsulGatewayProxy
// Ingress represents the Consul Configuration Entry for an Ingress Gateway.
Ingress *ConsulIngressConfigEntry
// Terminating is not yet supported.
// Terminating *ConsulTerminatingConfigEntry
// Mesh is not yet supported.
// Mesh *ConsulMeshConfigEntry
}
func (g *ConsulGateway) Canonicalize() {
if g == nil {
return
}
g.Proxy.Canonicalize()
g.Ingress.Canonicalize()
}
func (g *ConsulGateway) Copy() *ConsulGateway {
if g == nil {
return nil
}
return &ConsulGateway{
Proxy: g.Proxy.Copy(),
Ingress: g.Ingress.Copy(),
}
}
type ConsulGatewayBindAddress struct {
Address string `mapstructure:"address"`
Port int `mapstructure:"port"`
}
var (
defaultGatewayConnectTimeout = 5 * time.Second
)
// ConsulGatewayProxy is used to tune parameters of the proxy instance acting as
// one of the forms of Connect gateways that Consul supports.
//
// https://www.consul.io/docs/connect/proxies/envoy#gateway-options
type ConsulGatewayProxy struct {
ConnectTimeout *time.Duration `mapstructure:"connect_timeout"`
EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses"`
EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses"`
EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind"`
Config map[string]interface{} // escape hatch envoy config
}
func (p *ConsulGatewayProxy) Canonicalize() {
if p == nil {
return
}
if p.ConnectTimeout == nil {
// same as the default from consul
p.ConnectTimeout = timeToPtr(defaultGatewayConnectTimeout)
}
if len(p.EnvoyGatewayBindAddresses) == 0 {
p.EnvoyGatewayBindAddresses = nil
}
if len(p.Config) == 0 {
p.Config = nil
}
}
func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy {
if p == nil {
return nil
}
var binds map[string]*ConsulGatewayBindAddress = nil
if p.EnvoyGatewayBindAddresses != nil {
binds = make(map[string]*ConsulGatewayBindAddress, len(p.EnvoyGatewayBindAddresses))
for k, v := range p.EnvoyGatewayBindAddresses {
binds[k] = v
}
}
var config map[string]interface{} = nil
if p.Config != nil {
config = make(map[string]interface{}, len(p.Config))
for k, v := range p.Config {
config[k] = v
}
}
return &ConsulGatewayProxy{
ConnectTimeout: timeToPtr(*p.ConnectTimeout),
EnvoyGatewayBindTaggedAddresses: p.EnvoyGatewayBindTaggedAddresses,
EnvoyGatewayBindAddresses: binds,
EnvoyGatewayNoDefaultBind: p.EnvoyGatewayNoDefaultBind,
Config: config,
}
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway.
type ConsulGatewayTLSConfig struct {
Enabled bool
}
func (tc *ConsulGatewayTLSConfig) Canonicalize() {
}
func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
if tc == nil {
return nil
}
return &ConsulGatewayTLSConfig{
Enabled: tc.Enabled,
}
}
// ConsulIngressService is used to configure a service fronted by the ingress gateway.
type ConsulIngressService struct {
// Namespace is not yet supported.
// Namespace string
Name string
Hosts []string
}
func (s *ConsulIngressService) Canonicalize() {
if s == nil {
return
}
if len(s.Hosts) == 0 {
s.Hosts = nil
}
}
func (s *ConsulIngressService) Copy() *ConsulIngressService {
if s == nil {
return nil
}
var hosts []string = nil
if n := len(s.Hosts); n > 0 {
hosts = make([]string, n)
copy(hosts, s.Hosts)
}
return &ConsulIngressService{
Name: s.Name,
Hosts: hosts,
}
}
const (
defaultIngressListenerProtocol = "tcp"
)
// ConsulIngressListener is used to configure a listener on a Consul Ingress
// Gateway.
type ConsulIngressListener struct {
Port int
Protocol string
Services []*ConsulIngressService
}
func (l *ConsulIngressListener) Canonicalize() {
if l == nil {
return
}
if l.Protocol == "" {
// same as default from consul
l.Protocol = defaultIngressListenerProtocol
}
if len(l.Services) == 0 {
l.Services = nil
}
}
func (l *ConsulIngressListener) Copy() *ConsulIngressListener {
if l == nil {
return nil
}
var services []*ConsulIngressService = nil
if n := len(l.Services); n > 0 {
services = make([]*ConsulIngressService, n)
for i := 0; i < n; i++ {
services[i] = l.Services[i].Copy()
}
}
return &ConsulIngressListener{
Port: l.Port,
Protocol: l.Protocol,
Services: services,
}
}
// ConsulIngressConfigEntry represents the Consul Configuration Entry type for
// an Ingress Gateway.
//
// https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields
type ConsulIngressConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
TLS *ConsulGatewayTLSConfig
Listeners []*ConsulIngressListener
}
func (e *ConsulIngressConfigEntry) Canonicalize() {
if e == nil {
return
}
e.TLS.Canonicalize()
if len(e.Listeners) == 0 {
e.Listeners = nil
}
for _, listener := range e.Listeners {
listener.Canonicalize()
}
}
func (e *ConsulIngressConfigEntry) Copy() *ConsulIngressConfigEntry {
if e == nil {
return nil
}
var listeners []*ConsulIngressListener = nil
if n := len(e.Listeners); n > 0 {
listeners = make([]*ConsulIngressListener, n)
for i := 0; i < n; i++ {
listeners[i] = e.Listeners[i].Copy()
}
}
return &ConsulIngressConfigEntry{
TLS: e.TLS.Copy(),
Listeners: listeners,
}
}
// ConsulTerminatingConfigEntry is not yet supported.
// type ConsulTerminatingConfigEntry struct {
// }
// ConsulMeshConfigEntry is not yet supported.
// type ConsulMeshConfigEntry struct {
// }