322 lines
10 KiB
Go
322 lines
10 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"time"
|
|
)
|
|
|
|
// ServiceRegistration is an instance of a single allocation advertising itself
|
|
// as a named service with a specific address. Each registration is constructed
|
|
// from the job specification Service block. Whether the service is registered
|
|
// within Nomad, and therefore generates a ServiceRegistration is controlled by
|
|
// the Service.Provider parameter.
|
|
type ServiceRegistration struct {
|
|
|
|
// ID is the unique identifier for this registration. It currently follows
|
|
// the Consul service registration format to provide consistency between
|
|
// the two solutions.
|
|
ID string
|
|
|
|
// ServiceName is the human friendly identifier for this service
|
|
// registration.
|
|
ServiceName string
|
|
|
|
// Namespace represents the namespace within which this service is
|
|
// registered.
|
|
Namespace string
|
|
|
|
// NodeID is Node.ID on which this service registration is currently
|
|
// running.
|
|
NodeID string
|
|
|
|
// Datacenter is the DC identifier of the node as identified by
|
|
// Node.Datacenter.
|
|
Datacenter string
|
|
|
|
// JobID is Job.ID and represents the job which contained the service block
|
|
// which resulted in this service registration.
|
|
JobID string
|
|
|
|
// AllocID is Allocation.ID and represents the allocation within which this
|
|
// service is running.
|
|
AllocID string
|
|
|
|
// Tags are determined from either Service.Tags or Service.CanaryTags and
|
|
// help identify this service. Tags can also be used to perform lookups of
|
|
// services depending on their state and role.
|
|
Tags []string
|
|
|
|
// Address is the IP address of this service registration. This information
|
|
// comes from the client and is not guaranteed to be routable; this depends
|
|
// on cluster network topology.
|
|
Address string
|
|
|
|
// Port is the port number on which this service registration is bound. It
|
|
// is determined by a combination of factors on the client.
|
|
Port int
|
|
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
// ServiceRegistrationListStub represents all service registrations held within a
|
|
// single namespace.
|
|
type ServiceRegistrationListStub struct {
|
|
|
|
// Namespace details the namespace in which these services have been
|
|
// registered.
|
|
Namespace string
|
|
|
|
// Services is a list of services found within the namespace.
|
|
Services []*ServiceRegistrationStub
|
|
}
|
|
|
|
// ServiceRegistrationStub is the stub object describing an individual
|
|
// namespaced service. The object is built in a manner which would allow us to
|
|
// add additional fields in the future, if we wanted.
|
|
type ServiceRegistrationStub struct {
|
|
|
|
// ServiceName is the human friendly name for this service as specified
|
|
// within Service.Name.
|
|
ServiceName string
|
|
|
|
// Tags is a list of unique tags found for this service. The list is
|
|
// de-duplicated automatically by Nomad.
|
|
Tags []string
|
|
}
|
|
|
|
// Services is used to query the service endpoints.
|
|
type Services struct {
|
|
client *Client
|
|
}
|
|
|
|
// Services returns a new handle on the services endpoints.
|
|
func (c *Client) Services() *Services {
|
|
return &Services{client: c}
|
|
}
|
|
|
|
// List can be used to list all service registrations currently stored within
|
|
// the target namespace. It returns a stub response object.
|
|
func (s *Services) List(q *QueryOptions) ([]*ServiceRegistrationListStub, *QueryMeta, error) {
|
|
var resp []*ServiceRegistrationListStub
|
|
qm, err := s.client.query("/v1/services", &resp, q)
|
|
if err != nil {
|
|
return nil, qm, err
|
|
}
|
|
return resp, qm, nil
|
|
}
|
|
|
|
// Get is used to return a list of service registrations whose name matches the
|
|
// specified parameter.
|
|
func (s *Services) Get(serviceName string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) {
|
|
var resp []*ServiceRegistration
|
|
qm, err := s.client.query("/v1/service/"+url.PathEscape(serviceName), &resp, q)
|
|
if err != nil {
|
|
return nil, qm, err
|
|
}
|
|
return resp, qm, nil
|
|
}
|
|
|
|
// Delete can be used to delete an individual service registration as defined
|
|
// by its service name and service ID.
|
|
func (s *Services) Delete(serviceName, serviceID string, q *WriteOptions) (*WriteMeta, error) {
|
|
path := fmt.Sprintf("/v1/service/%s/%s", url.PathEscape(serviceName), url.PathEscape(serviceID))
|
|
wm, err := s.client.delete(path, nil, nil, q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return wm, nil
|
|
}
|
|
|
|
// CheckRestart describes if and when a task should be restarted based on
|
|
// failing health checks.
|
|
type CheckRestart struct {
|
|
Limit int `mapstructure:"limit" hcl:"limit,optional"`
|
|
Grace *time.Duration `mapstructure:"grace" hcl:"grace,optional"`
|
|
IgnoreWarnings bool `mapstructure:"ignore_warnings" hcl:"ignore_warnings,optional"`
|
|
}
|
|
|
|
// Canonicalize CheckRestart fields if not nil.
|
|
func (c *CheckRestart) Canonicalize() {
|
|
if c == nil {
|
|
return
|
|
}
|
|
|
|
if c.Grace == nil {
|
|
c.Grace = pointerOf(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 a Nomad job-submitters view of a Consul service health check.
|
|
type ServiceCheck struct {
|
|
Name string `hcl:"name,optional"`
|
|
Type string `hcl:"type,optional"`
|
|
Command string `hcl:"command,optional"`
|
|
Args []string `hcl:"args,optional"`
|
|
Path string `hcl:"path,optional"`
|
|
Protocol string `hcl:"protocol,optional"`
|
|
PortLabel string `mapstructure:"port" hcl:"port,optional"`
|
|
Expose bool `hcl:"expose,optional"`
|
|
AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"`
|
|
Advertise string `hcl:"advertise,optional"`
|
|
Interval time.Duration `hcl:"interval,optional"`
|
|
Timeout time.Duration `hcl:"timeout,optional"`
|
|
InitialStatus string `mapstructure:"initial_status" hcl:"initial_status,optional"`
|
|
TLSServerName string `mapstructure:"tls_server_name" hcl:"tls_server_name,optional"`
|
|
TLSSkipVerify bool `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"`
|
|
Header map[string][]string `hcl:"header,block"`
|
|
Method string `hcl:"method,optional"`
|
|
CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"`
|
|
GRPCService string `mapstructure:"grpc_service" hcl:"grpc_service,optional"`
|
|
GRPCUseTLS bool `mapstructure:"grpc_use_tls" hcl:"grpc_use_tls,optional"`
|
|
TaskName string `mapstructure:"task" hcl:"task,optional"`
|
|
SuccessBeforePassing int `mapstructure:"success_before_passing" hcl:"success_before_passing,optional"`
|
|
FailuresBeforeCritical int `mapstructure:"failures_before_critical" hcl:"failures_before_critical,optional"`
|
|
Body string `hcl:"body,optional"`
|
|
OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"`
|
|
}
|
|
|
|
// Service represents a Nomad job-submitters view of a Consul or Nomad service.
|
|
type Service struct {
|
|
Name string `hcl:"name,optional"`
|
|
Tags []string `hcl:"tags,optional"`
|
|
CanaryTags []string `mapstructure:"canary_tags" hcl:"canary_tags,optional"`
|
|
EnableTagOverride bool `mapstructure:"enable_tag_override" hcl:"enable_tag_override,optional"`
|
|
PortLabel string `mapstructure:"port" hcl:"port,optional"`
|
|
AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"`
|
|
Address string `hcl:"address,optional"`
|
|
Checks []ServiceCheck `hcl:"check,block"`
|
|
CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"`
|
|
Connect *ConsulConnect `hcl:"connect,block"`
|
|
Meta map[string]string `hcl:"meta,block"`
|
|
CanaryMeta map[string]string `hcl:"canary_meta,block"`
|
|
TaggedAddresses map[string]string `hcl:"tagged_addresses,block"`
|
|
TaskName string `mapstructure:"task" hcl:"task,optional"`
|
|
OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"`
|
|
|
|
// Provider defines which backend system provides the service registration,
|
|
// either "consul" (default) or "nomad".
|
|
Provider string `hcl:"provider,optional"`
|
|
}
|
|
|
|
const (
|
|
OnUpdateRequireHealthy = "require_healthy"
|
|
OnUpdateIgnoreWarn = "ignore_warnings"
|
|
OnUpdateIgnore = "ignore"
|
|
|
|
// ServiceProviderConsul is the default provider for services when no
|
|
// parameter is set.
|
|
ServiceProviderConsul = "consul"
|
|
)
|
|
|
|
// 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"
|
|
}
|
|
|
|
// Default to OnUpdateRequireHealthy
|
|
if s.OnUpdate == "" {
|
|
s.OnUpdate = OnUpdateRequireHealthy
|
|
}
|
|
|
|
// Default the service provider.
|
|
if s.Provider == "" {
|
|
s.Provider = ServiceProviderConsul
|
|
}
|
|
|
|
if len(s.Meta) == 0 {
|
|
s.Meta = nil
|
|
}
|
|
|
|
if len(s.CanaryMeta) == 0 {
|
|
s.CanaryMeta = nil
|
|
}
|
|
|
|
if len(s.TaggedAddresses) == 0 {
|
|
s.TaggedAddresses = nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Inhert Service
|
|
if s.Checks[i].OnUpdate == "" {
|
|
s.Checks[i].OnUpdate = s.OnUpdate
|
|
}
|
|
}
|
|
}
|