github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/command/agent/consul/client.go (about)

     1  package consul
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"net/url"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	metrics "github.com/armon/go-metrics"
    14  	"github.com/hashicorp/consul/api"
    15  	"github.com/hashicorp/nomad/client/driver"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  )
    18  
    19  const (
    20  	// nomadServicePrefix is the first prefix that scopes all Nomad registered
    21  	// services
    22  	nomadServicePrefix = "_nomad"
    23  
    24  	// defaultRetryInterval is how quickly to retry syncing services and
    25  	// checks to Consul when an error occurs. Will backoff up to a max.
    26  	defaultRetryInterval = time.Second
    27  
    28  	// defaultMaxRetryInterval is the default max retry interval.
    29  	defaultMaxRetryInterval = 30 * time.Second
    30  
    31  	// ttlCheckBuffer is the time interval that Nomad can take to report Consul
    32  	// the check result
    33  	ttlCheckBuffer = 31 * time.Second
    34  
    35  	// defaultShutdownWait is how long Shutdown() should block waiting for
    36  	// enqueued operations to sync to Consul by default.
    37  	defaultShutdownWait = time.Minute
    38  
    39  	// DefaultQueryWaitDuration is the max duration the Consul Agent will
    40  	// spend waiting for a response from a Consul Query.
    41  	DefaultQueryWaitDuration = 2 * time.Second
    42  
    43  	// ServiceTagHTTP is the tag assigned to HTTP services
    44  	ServiceTagHTTP = "http"
    45  
    46  	// ServiceTagRPC is the tag assigned to RPC services
    47  	ServiceTagRPC = "rpc"
    48  
    49  	// ServiceTagSerf is the tag assigned to Serf services
    50  	ServiceTagSerf = "serf"
    51  )
    52  
    53  // CatalogAPI is the consul/api.Catalog API used by Nomad.
    54  type CatalogAPI interface {
    55  	Datacenters() ([]string, error)
    56  	Service(service, tag string, q *api.QueryOptions) ([]*api.CatalogService, *api.QueryMeta, error)
    57  }
    58  
    59  // AgentAPI is the consul/api.Agent API used by Nomad.
    60  type AgentAPI interface {
    61  	Services() (map[string]*api.AgentService, error)
    62  	Checks() (map[string]*api.AgentCheck, error)
    63  	CheckRegister(check *api.AgentCheckRegistration) error
    64  	CheckDeregister(checkID string) error
    65  	ServiceRegister(service *api.AgentServiceRegistration) error
    66  	ServiceDeregister(serviceID string) error
    67  	UpdateTTL(id, output, status string) error
    68  }
    69  
    70  // addrParser is usually the Task.FindHostAndPortFor method for turning a
    71  // portLabel into an address and port.
    72  type addrParser func(portLabel string) (string, int)
    73  
    74  // operations are submitted to the main loop via commit() for synchronizing
    75  // with Consul.
    76  type operations struct {
    77  	regServices []*api.AgentServiceRegistration
    78  	regChecks   []*api.AgentCheckRegistration
    79  	scripts     []*scriptCheck
    80  
    81  	deregServices []string
    82  	deregChecks   []string
    83  }
    84  
    85  // ServiceClient handles task and agent service registration with Consul.
    86  type ServiceClient struct {
    87  	client           AgentAPI
    88  	logger           *log.Logger
    89  	retryInterval    time.Duration
    90  	maxRetryInterval time.Duration
    91  
    92  	// skipVerifySupport is true if the local Consul agent suppots TLSSkipVerify
    93  	skipVerifySupport bool
    94  
    95  	// exitCh is closed when the main Run loop exits
    96  	exitCh chan struct{}
    97  
    98  	// shutdownCh is closed when the client should shutdown
    99  	shutdownCh chan struct{}
   100  
   101  	// shutdownWait is how long Shutdown() blocks waiting for the final
   102  	// sync() to finish. Defaults to defaultShutdownWait
   103  	shutdownWait time.Duration
   104  
   105  	opCh chan *operations
   106  
   107  	services       map[string]*api.AgentServiceRegistration
   108  	checks         map[string]*api.AgentCheckRegistration
   109  	scripts        map[string]*scriptCheck
   110  	runningScripts map[string]*scriptHandle
   111  
   112  	// agent services and checks record entries for the agent itself which
   113  	// should be removed on shutdown
   114  	agentServices map[string]struct{}
   115  	agentChecks   map[string]struct{}
   116  	agentLock     sync.Mutex
   117  }
   118  
   119  // NewServiceClient creates a new Consul ServiceClient from an existing Consul API
   120  // Client and logger.
   121  func NewServiceClient(consulClient AgentAPI, skipVerifySupport bool, logger *log.Logger) *ServiceClient {
   122  	return &ServiceClient{
   123  		client:            consulClient,
   124  		skipVerifySupport: skipVerifySupport,
   125  		logger:            logger,
   126  		retryInterval:     defaultRetryInterval,
   127  		maxRetryInterval:  defaultMaxRetryInterval,
   128  		exitCh:            make(chan struct{}),
   129  		shutdownCh:        make(chan struct{}),
   130  		shutdownWait:      defaultShutdownWait,
   131  		opCh:              make(chan *operations, 8),
   132  		services:          make(map[string]*api.AgentServiceRegistration),
   133  		checks:            make(map[string]*api.AgentCheckRegistration),
   134  		scripts:           make(map[string]*scriptCheck),
   135  		runningScripts:    make(map[string]*scriptHandle),
   136  		agentServices:     make(map[string]struct{}),
   137  		agentChecks:       make(map[string]struct{}),
   138  	}
   139  }
   140  
   141  // Run the Consul main loop which retries operations against Consul. It should
   142  // be called exactly once.
   143  func (c *ServiceClient) Run() {
   144  	defer close(c.exitCh)
   145  	retryTimer := time.NewTimer(0)
   146  	<-retryTimer.C // disabled by default
   147  	failures := 0
   148  	for {
   149  		select {
   150  		case <-retryTimer.C:
   151  		case <-c.shutdownCh:
   152  		case ops := <-c.opCh:
   153  			c.merge(ops)
   154  		}
   155  
   156  		if err := c.sync(); err != nil {
   157  			if failures == 0 {
   158  				c.logger.Printf("[WARN] consul.sync: failed to update services in Consul: %v", err)
   159  			}
   160  			failures++
   161  			if !retryTimer.Stop() {
   162  				// Timer already expired, since the timer may
   163  				// or may not have been read in the select{}
   164  				// above, conditionally receive on it
   165  				select {
   166  				case <-retryTimer.C:
   167  				default:
   168  				}
   169  			}
   170  			backoff := c.retryInterval * time.Duration(failures)
   171  			if backoff > c.maxRetryInterval {
   172  				backoff = c.maxRetryInterval
   173  			}
   174  			retryTimer.Reset(backoff)
   175  		} else {
   176  			if failures > 0 {
   177  				c.logger.Printf("[INFO] consul.sync: successfully updated services in Consul")
   178  				failures = 0
   179  			}
   180  		}
   181  
   182  		select {
   183  		case <-c.shutdownCh:
   184  			// Exit only after sync'ing all outstanding operations
   185  			if len(c.opCh) > 0 {
   186  				for len(c.opCh) > 0 {
   187  					c.merge(<-c.opCh)
   188  				}
   189  				continue
   190  			}
   191  			return
   192  		default:
   193  		}
   194  
   195  	}
   196  }
   197  
   198  // commit operations unless already shutting down.
   199  func (c *ServiceClient) commit(ops *operations) {
   200  	select {
   201  	case c.opCh <- ops:
   202  	case <-c.shutdownCh:
   203  	}
   204  }
   205  
   206  // merge registrations into state map prior to sync'ing with Consul
   207  func (c *ServiceClient) merge(ops *operations) {
   208  	for _, s := range ops.regServices {
   209  		c.services[s.ID] = s
   210  	}
   211  	for _, check := range ops.regChecks {
   212  		c.checks[check.ID] = check
   213  	}
   214  	for _, s := range ops.scripts {
   215  		c.scripts[s.id] = s
   216  	}
   217  	for _, sid := range ops.deregServices {
   218  		delete(c.services, sid)
   219  	}
   220  	for _, cid := range ops.deregChecks {
   221  		if script, ok := c.runningScripts[cid]; ok {
   222  			script.cancel()
   223  			delete(c.scripts, cid)
   224  		}
   225  		delete(c.checks, cid)
   226  	}
   227  	metrics.SetGauge([]string{"client", "consul", "services"}, float32(len(c.services)))
   228  	metrics.SetGauge([]string{"client", "consul", "checks"}, float32(len(c.checks)))
   229  	metrics.SetGauge([]string{"client", "consul", "script_checks"}, float32(len(c.runningScripts)))
   230  }
   231  
   232  // sync enqueued operations.
   233  func (c *ServiceClient) sync() error {
   234  	sreg, creg, sdereg, cdereg := 0, 0, 0, 0
   235  
   236  	consulServices, err := c.client.Services()
   237  	if err != nil {
   238  		metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1)
   239  		return fmt.Errorf("error querying Consul services: %v", err)
   240  	}
   241  
   242  	consulChecks, err := c.client.Checks()
   243  	if err != nil {
   244  		metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1)
   245  		return fmt.Errorf("error querying Consul checks: %v", err)
   246  	}
   247  
   248  	// Remove Nomad services in Consul but unknown locally
   249  	for id := range consulServices {
   250  		if _, ok := c.services[id]; ok {
   251  			// Known service, skip
   252  			continue
   253  		}
   254  		if !isNomadService(id) {
   255  			// Not managed by Nomad, skip
   256  			continue
   257  		}
   258  		// Unknown Nomad managed service; kill
   259  		if err := c.client.ServiceDeregister(id); err != nil {
   260  			metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1)
   261  			return err
   262  		}
   263  		sdereg++
   264  		metrics.IncrCounter([]string{"client", "consul", "service_deregisrations"}, 1)
   265  	}
   266  
   267  	// Track services whose ports have changed as their checks may also
   268  	// need updating
   269  	portsChanged := make(map[string]struct{}, len(c.services))
   270  
   271  	// Add Nomad services missing from Consul
   272  	for id, locals := range c.services {
   273  		if remotes, ok := consulServices[id]; ok {
   274  			if locals.Port == remotes.Port {
   275  				// Already exists in Consul; skip
   276  				continue
   277  			}
   278  			// Port changed, reregister it and its checks
   279  			portsChanged[id] = struct{}{}
   280  		}
   281  		if err = c.client.ServiceRegister(locals); err != nil {
   282  			metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1)
   283  			return err
   284  		}
   285  		sreg++
   286  		metrics.IncrCounter([]string{"client", "consul", "service_regisrations"}, 1)
   287  	}
   288  
   289  	// Remove Nomad checks in Consul but unknown locally
   290  	for id, check := range consulChecks {
   291  		if _, ok := c.checks[id]; ok {
   292  			// Known check, leave it
   293  			continue
   294  		}
   295  		if !isNomadService(check.ServiceID) {
   296  			// Not managed by Nomad, skip
   297  			continue
   298  		}
   299  		// Unknown Nomad managed check; kill
   300  		if err := c.client.CheckDeregister(id); err != nil {
   301  			metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1)
   302  			return err
   303  		}
   304  		cdereg++
   305  		metrics.IncrCounter([]string{"client", "consul", "check_deregisrations"}, 1)
   306  	}
   307  
   308  	// Add Nomad checks missing from Consul
   309  	for id, check := range c.checks {
   310  		if check, ok := consulChecks[id]; ok {
   311  			if _, changed := portsChanged[check.ServiceID]; !changed {
   312  				// Already in Consul and ports didn't change; skipping
   313  				continue
   314  			}
   315  		}
   316  		if err := c.client.CheckRegister(check); err != nil {
   317  			metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1)
   318  			return err
   319  		}
   320  		creg++
   321  		metrics.IncrCounter([]string{"client", "consul", "check_regisrations"}, 1)
   322  
   323  		// Handle starting scripts
   324  		if script, ok := c.scripts[id]; ok {
   325  			// If it's already running, cancel and replace
   326  			if oldScript, running := c.runningScripts[id]; running {
   327  				oldScript.cancel()
   328  			}
   329  			// Start and store the handle
   330  			c.runningScripts[id] = script.run()
   331  		}
   332  	}
   333  
   334  	c.logger.Printf("[DEBUG] consul.sync: registered %d services, %d checks; deregistered %d services, %d checks",
   335  		sreg, creg, sdereg, cdereg)
   336  	return nil
   337  }
   338  
   339  // RegisterAgent registers Nomad agents (client or server). The
   340  // Service.PortLabel should be a literal port to be parsed with SplitHostPort.
   341  // Script checks are not supported and will return an error. Registration is
   342  // asynchronous.
   343  //
   344  // Agents will be deregistered when Shutdown is called.
   345  func (c *ServiceClient) RegisterAgent(role string, services []*structs.Service) error {
   346  	ops := operations{}
   347  
   348  	for _, service := range services {
   349  		id := makeAgentServiceID(role, service)
   350  
   351  		// Unlike tasks, agents don't use port labels. Agent ports are
   352  		// stored directly in the PortLabel.
   353  		host, rawport, err := net.SplitHostPort(service.PortLabel)
   354  		if err != nil {
   355  			return fmt.Errorf("error parsing port label %q from service %q: %v", service.PortLabel, service.Name, err)
   356  		}
   357  		port, err := strconv.Atoi(rawport)
   358  		if err != nil {
   359  			return fmt.Errorf("error parsing port %q from service %q: %v", rawport, service.Name, err)
   360  		}
   361  		serviceReg := &api.AgentServiceRegistration{
   362  			ID:      id,
   363  			Name:    service.Name,
   364  			Tags:    service.Tags,
   365  			Address: host,
   366  			Port:    port,
   367  		}
   368  		ops.regServices = append(ops.regServices, serviceReg)
   369  
   370  		for _, check := range service.Checks {
   371  			checkID := createCheckID(id, check)
   372  			if check.Type == structs.ServiceCheckScript {
   373  				return fmt.Errorf("service %q contains invalid check: agent checks do not support scripts", service.Name)
   374  			}
   375  			checkHost, checkPort := serviceReg.Address, serviceReg.Port
   376  			if check.PortLabel != "" {
   377  				// Unlike tasks, agents don't use port labels. Agent ports are
   378  				// stored directly in the PortLabel.
   379  				host, rawport, err := net.SplitHostPort(check.PortLabel)
   380  				if err != nil {
   381  					return fmt.Errorf("error parsing port label %q from check %q: %v", service.PortLabel, check.Name, err)
   382  				}
   383  				port, err := strconv.Atoi(rawport)
   384  				if err != nil {
   385  					return fmt.Errorf("error parsing port %q from check %q: %v", rawport, check.Name, err)
   386  				}
   387  				checkHost, checkPort = host, port
   388  			}
   389  			checkReg, err := createCheckReg(id, checkID, check, checkHost, checkPort)
   390  			if err != nil {
   391  				return fmt.Errorf("failed to add check %q: %v", check.Name, err)
   392  			}
   393  			ops.regChecks = append(ops.regChecks, checkReg)
   394  		}
   395  	}
   396  
   397  	// Don't bother committing agent checks if we're already shutting down
   398  	c.agentLock.Lock()
   399  	defer c.agentLock.Unlock()
   400  	select {
   401  	case <-c.shutdownCh:
   402  		return nil
   403  	default:
   404  	}
   405  
   406  	// Now add them to the registration queue
   407  	c.commit(&ops)
   408  
   409  	// Record IDs for deregistering on shutdown
   410  	for _, id := range ops.regServices {
   411  		c.agentServices[id.ID] = struct{}{}
   412  	}
   413  	for _, id := range ops.regChecks {
   414  		c.agentChecks[id.ID] = struct{}{}
   415  	}
   416  	return nil
   417  }
   418  
   419  // serviceRegs creates service registrations, check registrations, and script
   420  // checks from a service.
   421  func (c *ServiceClient) serviceRegs(ops *operations, allocID string, service *structs.Service,
   422  	exec driver.ScriptExecutor, task *structs.Task) error {
   423  
   424  	id := makeTaskServiceID(allocID, task.Name, service)
   425  	host, port := task.FindHostAndPortFor(service.PortLabel)
   426  	serviceReg := &api.AgentServiceRegistration{
   427  		ID:      id,
   428  		Name:    service.Name,
   429  		Tags:    make([]string, len(service.Tags)),
   430  		Address: host,
   431  		Port:    port,
   432  	}
   433  	// copy isn't strictly necessary but can avoid bugs especially
   434  	// with tests that may reuse Tasks
   435  	copy(serviceReg.Tags, service.Tags)
   436  	ops.regServices = append(ops.regServices, serviceReg)
   437  
   438  	for _, check := range service.Checks {
   439  		if check.TLSSkipVerify && !c.skipVerifySupport {
   440  			c.logger.Printf("[WARN] consul.sync: skipping check %q for task %q alloc %q because Consul doesn't support tls_skip_verify. Please upgrade to Consul >= 0.7.2.",
   441  				check.Name, task.Name, allocID)
   442  			continue
   443  		}
   444  		checkID := createCheckID(id, check)
   445  		if check.Type == structs.ServiceCheckScript {
   446  			if exec == nil {
   447  				return fmt.Errorf("driver doesn't support script checks")
   448  			}
   449  			ops.scripts = append(ops.scripts, newScriptCheck(
   450  				allocID, task.Name, checkID, check, exec, c.client, c.logger, c.shutdownCh))
   451  
   452  		}
   453  
   454  		host, port := serviceReg.Address, serviceReg.Port
   455  		if check.PortLabel != "" {
   456  			host, port = task.FindHostAndPortFor(check.PortLabel)
   457  		}
   458  		checkReg, err := createCheckReg(id, checkID, check, host, port)
   459  		if err != nil {
   460  			return fmt.Errorf("failed to add check %q: %v", check.Name, err)
   461  		}
   462  		ops.regChecks = append(ops.regChecks, checkReg)
   463  	}
   464  	return nil
   465  }
   466  
   467  // RegisterTask with Consul. Adds all sevice entries and checks to Consul. If
   468  // exec is nil and a script check exists an error is returned.
   469  //
   470  // Actual communication with Consul is done asynchrously (see Run).
   471  func (c *ServiceClient) RegisterTask(allocID string, task *structs.Task, exec driver.ScriptExecutor) error {
   472  	ops := &operations{}
   473  	for _, service := range task.Services {
   474  		if err := c.serviceRegs(ops, allocID, service, exec, task); err != nil {
   475  			return err
   476  		}
   477  	}
   478  	c.commit(ops)
   479  	return nil
   480  }
   481  
   482  // UpdateTask in Consul. Does not alter the service if only checks have
   483  // changed.
   484  func (c *ServiceClient) UpdateTask(allocID string, existing, newTask *structs.Task, exec driver.ScriptExecutor) error {
   485  	ops := &operations{}
   486  
   487  	existingIDs := make(map[string]*structs.Service, len(existing.Services))
   488  	for _, s := range existing.Services {
   489  		existingIDs[makeTaskServiceID(allocID, existing.Name, s)] = s
   490  	}
   491  	newIDs := make(map[string]*structs.Service, len(newTask.Services))
   492  	for _, s := range newTask.Services {
   493  		newIDs[makeTaskServiceID(allocID, newTask.Name, s)] = s
   494  	}
   495  
   496  	// Loop over existing Service IDs to see if they have been removed or
   497  	// updated.
   498  	for existingID, existingSvc := range existingIDs {
   499  		newSvc, ok := newIDs[existingID]
   500  		if !ok {
   501  			// Existing sevice entry removed
   502  			ops.deregServices = append(ops.deregServices, existingID)
   503  			for _, check := range existingSvc.Checks {
   504  				ops.deregChecks = append(ops.deregChecks, createCheckID(existingID, check))
   505  			}
   506  			continue
   507  		}
   508  
   509  		if newSvc.PortLabel == existingSvc.PortLabel {
   510  			// Service exists and hasn't changed, don't add it later
   511  			delete(newIDs, existingID)
   512  		}
   513  
   514  		// Check to see what checks were updated
   515  		existingChecks := make(map[string]struct{}, len(existingSvc.Checks))
   516  		for _, check := range existingSvc.Checks {
   517  			existingChecks[createCheckID(existingID, check)] = struct{}{}
   518  		}
   519  
   520  		// Register new checks
   521  		for _, check := range newSvc.Checks {
   522  			checkID := createCheckID(existingID, check)
   523  			if _, exists := existingChecks[checkID]; exists {
   524  				// Check exists, so don't remove it
   525  				delete(existingChecks, checkID)
   526  			}
   527  		}
   528  
   529  		// Remove existing checks not in updated service
   530  		for cid := range existingChecks {
   531  			ops.deregChecks = append(ops.deregChecks, cid)
   532  		}
   533  	}
   534  
   535  	// Any remaining services should just be enqueued directly
   536  	for _, newSvc := range newIDs {
   537  		err := c.serviceRegs(ops, allocID, newSvc, exec, newTask)
   538  		if err != nil {
   539  			return err
   540  		}
   541  	}
   542  
   543  	c.commit(ops)
   544  	return nil
   545  }
   546  
   547  // RemoveTask from Consul. Removes all service entries and checks.
   548  //
   549  // Actual communication with Consul is done asynchrously (see Run).
   550  func (c *ServiceClient) RemoveTask(allocID string, task *structs.Task) {
   551  	ops := operations{}
   552  
   553  	for _, service := range task.Services {
   554  		id := makeTaskServiceID(allocID, task.Name, service)
   555  		ops.deregServices = append(ops.deregServices, id)
   556  
   557  		for _, check := range service.Checks {
   558  			ops.deregChecks = append(ops.deregChecks, createCheckID(id, check))
   559  		}
   560  	}
   561  
   562  	// Now add them to the deregistration fields; main Run loop will update
   563  	c.commit(&ops)
   564  }
   565  
   566  // Shutdown the Consul client. Update running task registations and deregister
   567  // agent from Consul. On first call blocks up to shutdownWait before giving up
   568  // on syncing operations.
   569  func (c *ServiceClient) Shutdown() error {
   570  	// Serialize Shutdown calls with RegisterAgent to prevent leaking agent
   571  	// entries.
   572  	c.agentLock.Lock()
   573  	select {
   574  	case <-c.shutdownCh:
   575  		return nil
   576  	default:
   577  	}
   578  
   579  	// Deregister Nomad agent Consul entries before closing shutdown.
   580  	ops := operations{}
   581  	for id := range c.agentServices {
   582  		ops.deregServices = append(ops.deregServices, id)
   583  	}
   584  	for id := range c.agentChecks {
   585  		ops.deregChecks = append(ops.deregChecks, id)
   586  	}
   587  	c.commit(&ops)
   588  
   589  	// Then signal shutdown
   590  	close(c.shutdownCh)
   591  
   592  	// Safe to unlock after shutdownCh closed as RegisterAgent will check
   593  	// shutdownCh before committing.
   594  	c.agentLock.Unlock()
   595  
   596  	// Give run loop time to sync, but don't block indefinitely
   597  	deadline := time.After(c.shutdownWait)
   598  
   599  	// Wait for Run to finish any outstanding operations and exit
   600  	select {
   601  	case <-c.exitCh:
   602  	case <-deadline:
   603  		// Don't wait forever though
   604  		return fmt.Errorf("timed out waiting for Consul operations to complete")
   605  	}
   606  
   607  	// Give script checks time to exit (no need to lock as Run() has exited)
   608  	for _, h := range c.runningScripts {
   609  		select {
   610  		case <-h.wait():
   611  		case <-deadline:
   612  			return fmt.Errorf("timed out waiting for script checks to run")
   613  		}
   614  	}
   615  	return nil
   616  }
   617  
   618  // makeAgentServiceID creates a unique ID for identifying an agent service in
   619  // Consul.
   620  //
   621  // Agent service IDs are of the form:
   622  //
   623  //	{nomadServicePrefix}-{ROLE}-{Service.Name}-{Service.Tags...}
   624  //	Example Server ID: _nomad-server-nomad-serf
   625  //	Example Client ID: _nomad-client-nomad-client-http
   626  //
   627  func makeAgentServiceID(role string, service *structs.Service) string {
   628  	parts := make([]string, len(service.Tags)+3)
   629  	parts[0] = nomadServicePrefix
   630  	parts[1] = role
   631  	parts[2] = service.Name
   632  	copy(parts[3:], service.Tags)
   633  	return strings.Join(parts, "-")
   634  }
   635  
   636  // makeTaskServiceID creates a unique ID for identifying a task service in
   637  // Consul.
   638  //
   639  // Task service IDs are of the form:
   640  //
   641  //	{nomadServicePrefix}-executor-{ALLOC_ID}-{Service.Name}-{Service.Tags...}
   642  //	Example Service ID: _nomad-executor-1234-echo-http-tag1-tag2-tag3
   643  //
   644  func makeTaskServiceID(allocID, taskName string, service *structs.Service) string {
   645  	parts := make([]string, len(service.Tags)+5)
   646  	parts[0] = nomadServicePrefix
   647  	parts[1] = "executor"
   648  	parts[2] = allocID
   649  	parts[3] = taskName
   650  	parts[4] = service.Name
   651  	copy(parts[5:], service.Tags)
   652  	return strings.Join(parts, "-")
   653  }
   654  
   655  // createCheckID creates a unique ID for a check.
   656  func createCheckID(serviceID string, check *structs.ServiceCheck) string {
   657  	return check.Hash(serviceID)
   658  }
   659  
   660  // createCheckReg creates a Check that can be registered with Consul.
   661  //
   662  // Script checks simply have a TTL set and the caller is responsible for
   663  // running the script and heartbeating.
   664  func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host string, port int) (*api.AgentCheckRegistration, error) {
   665  	chkReg := api.AgentCheckRegistration{
   666  		ID:        checkID,
   667  		Name:      check.Name,
   668  		ServiceID: serviceID,
   669  	}
   670  	chkReg.Status = check.InitialStatus
   671  	chkReg.Timeout = check.Timeout.String()
   672  	chkReg.Interval = check.Interval.String()
   673  
   674  	switch check.Type {
   675  	case structs.ServiceCheckHTTP:
   676  		if check.Protocol == "" {
   677  			check.Protocol = "http"
   678  		}
   679  		if check.TLSSkipVerify {
   680  			chkReg.TLSSkipVerify = true
   681  		}
   682  		base := url.URL{
   683  			Scheme: check.Protocol,
   684  			Host:   net.JoinHostPort(host, strconv.Itoa(port)),
   685  		}
   686  		relative, err := url.Parse(check.Path)
   687  		if err != nil {
   688  			return nil, err
   689  		}
   690  		url := base.ResolveReference(relative)
   691  		chkReg.HTTP = url.String()
   692  	case structs.ServiceCheckTCP:
   693  		chkReg.TCP = net.JoinHostPort(host, strconv.Itoa(port))
   694  	case structs.ServiceCheckScript:
   695  		chkReg.TTL = (check.Interval + ttlCheckBuffer).String()
   696  	default:
   697  		return nil, fmt.Errorf("check type %+q not valid", check.Type)
   698  	}
   699  	return &chkReg, nil
   700  }
   701  
   702  // isNomadService returns true if the ID matches the pattern of a Nomad managed
   703  // service.
   704  func isNomadService(id string) bool {
   705  	return strings.HasPrefix(id, nomadServicePrefix)
   706  }