github.com/hashicorp/nomad/api@v0.0.0-20240306165712-3193ac204f65/services.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  	"time"
    10  )
    11  
    12  // ServiceRegistration is an instance of a single allocation advertising itself
    13  // as a named service with a specific address. Each registration is constructed
    14  // from the job specification Service block. Whether the service is registered
    15  // within Nomad, and therefore generates a ServiceRegistration is controlled by
    16  // the Service.Provider parameter.
    17  type ServiceRegistration struct {
    18  
    19  	// ID is the unique identifier for this registration. It currently follows
    20  	// the Consul service registration format to provide consistency between
    21  	// the two solutions.
    22  	ID string
    23  
    24  	// ServiceName is the human friendly identifier for this service
    25  	// registration.
    26  	ServiceName string
    27  
    28  	// Namespace represents the namespace within which this service is
    29  	// registered.
    30  	Namespace string
    31  
    32  	// NodeID is Node.ID on which this service registration is currently
    33  	// running.
    34  	NodeID string
    35  
    36  	// Datacenter is the DC identifier of the node as identified by
    37  	// Node.Datacenter.
    38  	Datacenter string
    39  
    40  	// JobID is Job.ID and represents the job which contained the service block
    41  	// which resulted in this service registration.
    42  	JobID string
    43  
    44  	// AllocID is Allocation.ID and represents the allocation within which this
    45  	// service is running.
    46  	AllocID string
    47  
    48  	// Tags are determined from either Service.Tags or Service.CanaryTags and
    49  	// help identify this service. Tags can also be used to perform lookups of
    50  	// services depending on their state and role.
    51  	Tags []string
    52  
    53  	// Address is the IP address of this service registration. This information
    54  	// comes from the client and is not guaranteed to be routable; this depends
    55  	// on cluster network topology.
    56  	Address string
    57  
    58  	// Port is the port number on which this service registration is bound. It
    59  	// is determined by a combination of factors on the client.
    60  	Port int
    61  
    62  	CreateIndex uint64
    63  	ModifyIndex uint64
    64  }
    65  
    66  // ServiceRegistrationListStub represents all service registrations held within a
    67  // single namespace.
    68  type ServiceRegistrationListStub struct {
    69  
    70  	// Namespace details the namespace in which these services have been
    71  	// registered.
    72  	Namespace string
    73  
    74  	// Services is a list of services found within the namespace.
    75  	Services []*ServiceRegistrationStub
    76  }
    77  
    78  // ServiceRegistrationStub is the stub object describing an individual
    79  // namespaced service. The object is built in a manner which would allow us to
    80  // add additional fields in the future, if we wanted.
    81  type ServiceRegistrationStub struct {
    82  
    83  	// ServiceName is the human friendly name for this service as specified
    84  	// within Service.Name.
    85  	ServiceName string
    86  
    87  	// Tags is a list of unique tags found for this service. The list is
    88  	// de-duplicated automatically by Nomad.
    89  	Tags []string
    90  }
    91  
    92  // Services is used to query the service endpoints.
    93  type Services struct {
    94  	client *Client
    95  }
    96  
    97  // Services returns a new handle on the services endpoints.
    98  func (c *Client) Services() *Services {
    99  	return &Services{client: c}
   100  }
   101  
   102  // List can be used to list all service registrations currently stored within
   103  // the target namespace. It returns a stub response object.
   104  func (s *Services) List(q *QueryOptions) ([]*ServiceRegistrationListStub, *QueryMeta, error) {
   105  	var resp []*ServiceRegistrationListStub
   106  	qm, err := s.client.query("/v1/services", &resp, q)
   107  	if err != nil {
   108  		return nil, qm, err
   109  	}
   110  	return resp, qm, nil
   111  }
   112  
   113  // Get is used to return a list of service registrations whose name matches the
   114  // specified parameter.
   115  func (s *Services) Get(serviceName string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) {
   116  	var resp []*ServiceRegistration
   117  	qm, err := s.client.query("/v1/service/"+url.PathEscape(serviceName), &resp, q)
   118  	if err != nil {
   119  		return nil, qm, err
   120  	}
   121  	return resp, qm, nil
   122  }
   123  
   124  // Delete can be used to delete an individual service registration as defined
   125  // by its service name and service ID.
   126  func (s *Services) Delete(serviceName, serviceID string, q *WriteOptions) (*WriteMeta, error) {
   127  	path := fmt.Sprintf("/v1/service/%s/%s", url.PathEscape(serviceName), url.PathEscape(serviceID))
   128  	wm, err := s.client.delete(path, nil, nil, q)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	return wm, nil
   133  }
   134  
   135  // CheckRestart describes if and when a task should be restarted based on
   136  // failing health checks.
   137  type CheckRestart struct {
   138  	Limit          int            `mapstructure:"limit" hcl:"limit,optional"`
   139  	Grace          *time.Duration `mapstructure:"grace" hcl:"grace,optional"`
   140  	IgnoreWarnings bool           `mapstructure:"ignore_warnings" hcl:"ignore_warnings,optional"`
   141  }
   142  
   143  // Canonicalize CheckRestart fields if not nil.
   144  func (c *CheckRestart) Canonicalize() {
   145  	if c == nil {
   146  		return
   147  	}
   148  
   149  	if c.Grace == nil {
   150  		c.Grace = pointerOf(1 * time.Second)
   151  	}
   152  }
   153  
   154  // Copy returns a copy of CheckRestart or nil if unset.
   155  func (c *CheckRestart) Copy() *CheckRestart {
   156  	if c == nil {
   157  		return nil
   158  	}
   159  
   160  	nc := new(CheckRestart)
   161  	nc.Limit = c.Limit
   162  	if c.Grace != nil {
   163  		g := *c.Grace
   164  		nc.Grace = &g
   165  	}
   166  	nc.IgnoreWarnings = c.IgnoreWarnings
   167  	return nc
   168  }
   169  
   170  // Merge values from other CheckRestart over default values on this
   171  // CheckRestart and return merged copy.
   172  func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart {
   173  	if c == nil {
   174  		// Just return other
   175  		return o
   176  	}
   177  
   178  	nc := c.Copy()
   179  
   180  	if o == nil {
   181  		// Nothing to merge
   182  		return nc
   183  	}
   184  
   185  	if o.Limit > 0 {
   186  		nc.Limit = o.Limit
   187  	}
   188  
   189  	if o.Grace != nil {
   190  		nc.Grace = o.Grace
   191  	}
   192  
   193  	if o.IgnoreWarnings {
   194  		nc.IgnoreWarnings = o.IgnoreWarnings
   195  	}
   196  
   197  	return nc
   198  }
   199  
   200  // ServiceCheck represents a Nomad job-submitters view of a Consul service health check.
   201  type ServiceCheck struct {
   202  	Name                   string              `hcl:"name,optional"`
   203  	Type                   string              `hcl:"type,optional"`
   204  	Command                string              `hcl:"command,optional"`
   205  	Args                   []string            `hcl:"args,optional"`
   206  	Path                   string              `hcl:"path,optional"`
   207  	Protocol               string              `hcl:"protocol,optional"`
   208  	PortLabel              string              `mapstructure:"port" hcl:"port,optional"`
   209  	Expose                 bool                `hcl:"expose,optional"`
   210  	AddressMode            string              `mapstructure:"address_mode" hcl:"address_mode,optional"`
   211  	Advertise              string              `hcl:"advertise,optional"`
   212  	Interval               time.Duration       `hcl:"interval,optional"`
   213  	Timeout                time.Duration       `hcl:"timeout,optional"`
   214  	InitialStatus          string              `mapstructure:"initial_status" hcl:"initial_status,optional"`
   215  	TLSServerName          string              `mapstructure:"tls_server_name" hcl:"tls_server_name,optional"`
   216  	TLSSkipVerify          bool                `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"`
   217  	Header                 map[string][]string `hcl:"header,block"`
   218  	Method                 string              `hcl:"method,optional"`
   219  	CheckRestart           *CheckRestart       `mapstructure:"check_restart" hcl:"check_restart,block"`
   220  	GRPCService            string              `mapstructure:"grpc_service" hcl:"grpc_service,optional"`
   221  	GRPCUseTLS             bool                `mapstructure:"grpc_use_tls" hcl:"grpc_use_tls,optional"`
   222  	TaskName               string              `mapstructure:"task" hcl:"task,optional"`
   223  	SuccessBeforePassing   int                 `mapstructure:"success_before_passing" hcl:"success_before_passing,optional"`
   224  	FailuresBeforeCritical int                 `mapstructure:"failures_before_critical" hcl:"failures_before_critical,optional"`
   225  	FailuresBeforeWarning  int                 `mapstructure:"failures_before_warning" hcl:"failures_before_warning,optional"`
   226  	Body                   string              `hcl:"body,optional"`
   227  	OnUpdate               string              `mapstructure:"on_update" hcl:"on_update,optional"`
   228  }
   229  
   230  // Service represents a Nomad job-submitters view of a Consul or Nomad service.
   231  type Service struct {
   232  	Name              string            `hcl:"name,optional"`
   233  	Tags              []string          `hcl:"tags,optional"`
   234  	CanaryTags        []string          `mapstructure:"canary_tags" hcl:"canary_tags,optional"`
   235  	EnableTagOverride bool              `mapstructure:"enable_tag_override" hcl:"enable_tag_override,optional"`
   236  	PortLabel         string            `mapstructure:"port" hcl:"port,optional"`
   237  	AddressMode       string            `mapstructure:"address_mode" hcl:"address_mode,optional"`
   238  	Address           string            `hcl:"address,optional"`
   239  	Checks            []ServiceCheck    `hcl:"check,block"`
   240  	CheckRestart      *CheckRestart     `mapstructure:"check_restart" hcl:"check_restart,block"`
   241  	Connect           *ConsulConnect    `hcl:"connect,block"`
   242  	Meta              map[string]string `hcl:"meta,block"`
   243  	CanaryMeta        map[string]string `hcl:"canary_meta,block"`
   244  	TaggedAddresses   map[string]string `hcl:"tagged_addresses,block"`
   245  	TaskName          string            `mapstructure:"task" hcl:"task,optional"`
   246  	OnUpdate          string            `mapstructure:"on_update" hcl:"on_update,optional"`
   247  	Identity          *WorkloadIdentity `hcl:"identity,block"`
   248  
   249  	// Provider defines which backend system provides the service registration,
   250  	// either "consul" (default) or "nomad".
   251  	Provider string `hcl:"provider,optional"`
   252  
   253  	// Cluster is valid only for Nomad Enterprise with provider: consul
   254  	Cluster string `hcl:"cluster,optional"`
   255  }
   256  
   257  const (
   258  	OnUpdateRequireHealthy = "require_healthy"
   259  	OnUpdateIgnoreWarn     = "ignore_warnings"
   260  	OnUpdateIgnore         = "ignore"
   261  
   262  	// ServiceProviderConsul is the default provider for services when no
   263  	// parameter is set.
   264  	ServiceProviderConsul = "consul"
   265  )
   266  
   267  // Canonicalize the Service by ensuring its name and address mode are set. Task
   268  // will be nil for group services.
   269  func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) {
   270  	if s.Name == "" {
   271  		if t != nil {
   272  			s.Name = fmt.Sprintf("%s-%s-%s", *job.Name, *tg.Name, t.Name)
   273  		} else {
   274  			s.Name = fmt.Sprintf("%s-%s", *job.Name, *tg.Name)
   275  		}
   276  	}
   277  
   278  	// Default to AddressModeAuto
   279  	if s.AddressMode == "" {
   280  		s.AddressMode = "auto"
   281  	}
   282  
   283  	// Default to OnUpdateRequireHealthy
   284  	if s.OnUpdate == "" {
   285  		s.OnUpdate = OnUpdateRequireHealthy
   286  	}
   287  
   288  	// Default the service provider.
   289  	if s.Provider == "" {
   290  		s.Provider = ServiceProviderConsul
   291  	}
   292  	if s.Cluster == "" {
   293  		s.Cluster = "default"
   294  	}
   295  
   296  	if len(s.Meta) == 0 {
   297  		s.Meta = nil
   298  	}
   299  
   300  	if len(s.CanaryMeta) == 0 {
   301  		s.CanaryMeta = nil
   302  	}
   303  
   304  	if len(s.TaggedAddresses) == 0 {
   305  		s.TaggedAddresses = nil
   306  	}
   307  
   308  	s.Connect.Canonicalize()
   309  
   310  	// Canonicalize CheckRestart on Checks and merge Service.CheckRestart
   311  	// into each check.
   312  	for i, check := range s.Checks {
   313  		s.Checks[i].CheckRestart = s.CheckRestart.Merge(check.CheckRestart)
   314  		s.Checks[i].CheckRestart.Canonicalize()
   315  
   316  		if s.Checks[i].SuccessBeforePassing < 0 {
   317  			s.Checks[i].SuccessBeforePassing = 0
   318  		}
   319  
   320  		if s.Checks[i].FailuresBeforeCritical < 0 {
   321  			s.Checks[i].FailuresBeforeCritical = 0
   322  		}
   323  
   324  		if s.Checks[i].FailuresBeforeWarning < 0 {
   325  			s.Checks[i].FailuresBeforeWarning = 0
   326  		}
   327  
   328  		// Inhert Service
   329  		if s.Checks[i].OnUpdate == "" {
   330  			s.Checks[i].OnUpdate = s.OnUpdate
   331  		}
   332  	}
   333  }