github.com/smithx10/nomad@v0.9.1-rc1/client/allocrunner/taskrunner/service_hook.go (about)

     1  package taskrunner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	log "github.com/hashicorp/go-hclog"
    10  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
    11  	tinterfaces "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
    12  	"github.com/hashicorp/nomad/client/consul"
    13  	"github.com/hashicorp/nomad/client/taskenv"
    14  	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/hashicorp/nomad/plugins/drivers"
    17  )
    18  
    19  type serviceHookConfig struct {
    20  	alloc  *structs.Allocation
    21  	task   *structs.Task
    22  	consul consul.ConsulServiceAPI
    23  
    24  	// Restarter is a subset of the TaskLifecycle interface
    25  	restarter agentconsul.TaskRestarter
    26  
    27  	logger log.Logger
    28  }
    29  
    30  type serviceHook struct {
    31  	consul    consul.ConsulServiceAPI
    32  	allocID   string
    33  	taskName  string
    34  	restarter agentconsul.TaskRestarter
    35  	logger    log.Logger
    36  
    37  	// The following fields may be updated
    38  	delay      time.Duration
    39  	driverExec tinterfaces.ScriptExecutor
    40  	driverNet  *drivers.DriverNetwork
    41  	canary     bool
    42  	services   []*structs.Service
    43  	networks   structs.Networks
    44  	taskEnv    *taskenv.TaskEnv
    45  
    46  	// Since Update() may be called concurrently with any other hook all
    47  	// hook methods must be fully serialized
    48  	mu sync.Mutex
    49  }
    50  
    51  func newServiceHook(c serviceHookConfig) *serviceHook {
    52  	h := &serviceHook{
    53  		consul:    c.consul,
    54  		allocID:   c.alloc.ID,
    55  		taskName:  c.task.Name,
    56  		services:  c.task.Services,
    57  		restarter: c.restarter,
    58  		delay:     c.task.ShutdownDelay,
    59  	}
    60  
    61  	// COMPAT(0.10): Just use the AllocatedResources
    62  	if c.alloc.AllocatedResources != nil {
    63  		if res := c.alloc.AllocatedResources.Tasks[c.task.Name]; res != nil {
    64  			h.networks = res.Networks
    65  		}
    66  	} else {
    67  		if res := c.alloc.TaskResources[c.task.Name]; res != nil {
    68  			h.networks = res.Networks
    69  		}
    70  	}
    71  
    72  	if c.alloc.DeploymentStatus != nil && c.alloc.DeploymentStatus.Canary {
    73  		h.canary = true
    74  	}
    75  
    76  	h.logger = c.logger.Named(h.Name())
    77  	return h
    78  }
    79  
    80  func (h *serviceHook) Name() string {
    81  	return "consul_services"
    82  }
    83  
    84  func (h *serviceHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, _ *interfaces.TaskPoststartResponse) error {
    85  	h.mu.Lock()
    86  	defer h.mu.Unlock()
    87  
    88  	// Store the TaskEnv for interpolating now and when Updating
    89  	h.driverExec = req.DriverExec
    90  	h.driverNet = req.DriverNetwork
    91  	h.taskEnv = req.TaskEnv
    92  
    93  	// Create task services struct with request's driver metadata
    94  	taskServices := h.getTaskServices()
    95  
    96  	return h.consul.RegisterTask(taskServices)
    97  }
    98  
    99  func (h *serviceHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, _ *interfaces.TaskUpdateResponse) error {
   100  	h.mu.Lock()
   101  	defer h.mu.Unlock()
   102  
   103  	// Create old task services struct with request's driver metadata as it
   104  	// can't change due to Updates
   105  	oldTaskServices := h.getTaskServices()
   106  
   107  	// Store new updated values out of request
   108  	canary := false
   109  	if req.Alloc.DeploymentStatus != nil {
   110  		canary = req.Alloc.DeploymentStatus.Canary
   111  	}
   112  
   113  	// COMPAT(0.10): Just use the AllocatedResources
   114  	var networks structs.Networks
   115  	if req.Alloc.AllocatedResources != nil {
   116  		if res := req.Alloc.AllocatedResources.Tasks[h.taskName]; res != nil {
   117  			networks = res.Networks
   118  		}
   119  	} else {
   120  		if res := req.Alloc.TaskResources[h.taskName]; res != nil {
   121  			networks = res.Networks
   122  		}
   123  	}
   124  
   125  	task := req.Alloc.LookupTask(h.taskName)
   126  	if task == nil {
   127  		return fmt.Errorf("task %q not found in updated alloc", h.taskName)
   128  	}
   129  
   130  	// Update service hook fields
   131  	h.delay = task.ShutdownDelay
   132  	h.taskEnv = req.TaskEnv
   133  	h.services = task.Services
   134  	h.networks = networks
   135  	h.canary = canary
   136  
   137  	// Create new task services struct with those new values
   138  	newTaskServices := h.getTaskServices()
   139  
   140  	return h.consul.UpdateTask(oldTaskServices, newTaskServices)
   141  }
   142  
   143  func (h *serviceHook) PreKilling(ctx context.Context, req *interfaces.TaskPreKillRequest, resp *interfaces.TaskPreKillResponse) error {
   144  	h.mu.Lock()
   145  	defer h.mu.Unlock()
   146  
   147  	// Deregister before killing task
   148  	h.deregister()
   149  
   150  	// If there's no shutdown delay, exit early
   151  	if h.delay == 0 {
   152  		return nil
   153  	}
   154  
   155  	h.logger.Debug("waiting before killing task", "shutdown_delay", h.delay)
   156  	select {
   157  	case <-ctx.Done():
   158  	case <-time.After(h.delay):
   159  	}
   160  	return nil
   161  }
   162  
   163  func (h *serviceHook) Exited(context.Context, *interfaces.TaskExitedRequest, *interfaces.TaskExitedResponse) error {
   164  	h.mu.Lock()
   165  	defer h.mu.Unlock()
   166  	h.deregister()
   167  	return nil
   168  }
   169  
   170  // deregister services from Consul.
   171  func (h *serviceHook) deregister() {
   172  	taskServices := h.getTaskServices()
   173  	h.consul.RemoveTask(taskServices)
   174  
   175  	// Canary flag may be getting flipped when the alloc is being
   176  	// destroyed, so remove both variations of the service
   177  	taskServices.Canary = !taskServices.Canary
   178  	h.consul.RemoveTask(taskServices)
   179  
   180  }
   181  
   182  func (h *serviceHook) getTaskServices() *agentconsul.TaskServices {
   183  	// Interpolate with the task's environment
   184  	interpolatedServices := interpolateServices(h.taskEnv, h.services)
   185  
   186  	// Create task services struct with request's driver metadata
   187  	return &agentconsul.TaskServices{
   188  		AllocID:       h.allocID,
   189  		Name:          h.taskName,
   190  		Restarter:     h.restarter,
   191  		Services:      interpolatedServices,
   192  		DriverExec:    h.driverExec,
   193  		DriverNetwork: h.driverNet,
   194  		Networks:      h.networks,
   195  		Canary:        h.canary,
   196  	}
   197  }
   198  
   199  // interpolateServices returns an interpolated copy of services and checks with
   200  // values from the task's environment.
   201  func interpolateServices(taskEnv *taskenv.TaskEnv, services []*structs.Service) []*structs.Service {
   202  	// Guard against not having a valid taskEnv. This can be the case if the
   203  	// PreKilling or Exited hook is run before Poststart.
   204  	if taskEnv == nil || len(services) == 0 {
   205  		return nil
   206  	}
   207  
   208  	interpolated := make([]*structs.Service, len(services))
   209  
   210  	for i, origService := range services {
   211  		// Create a copy as we need to reinterpolate every time the
   212  		// environment changes
   213  		service := origService.Copy()
   214  
   215  		for _, check := range service.Checks {
   216  			check.Name = taskEnv.ReplaceEnv(check.Name)
   217  			check.Type = taskEnv.ReplaceEnv(check.Type)
   218  			check.Command = taskEnv.ReplaceEnv(check.Command)
   219  			check.Args = taskEnv.ParseAndReplace(check.Args)
   220  			check.Path = taskEnv.ReplaceEnv(check.Path)
   221  			check.Protocol = taskEnv.ReplaceEnv(check.Protocol)
   222  			check.PortLabel = taskEnv.ReplaceEnv(check.PortLabel)
   223  			check.InitialStatus = taskEnv.ReplaceEnv(check.InitialStatus)
   224  			check.Method = taskEnv.ReplaceEnv(check.Method)
   225  			check.GRPCService = taskEnv.ReplaceEnv(check.GRPCService)
   226  			if len(check.Header) > 0 {
   227  				header := make(map[string][]string, len(check.Header))
   228  				for k, vs := range check.Header {
   229  					newVals := make([]string, len(vs))
   230  					for i, v := range vs {
   231  						newVals[i] = taskEnv.ReplaceEnv(v)
   232  					}
   233  					header[taskEnv.ReplaceEnv(k)] = newVals
   234  				}
   235  				check.Header = header
   236  			}
   237  		}
   238  
   239  		service.Name = taskEnv.ReplaceEnv(service.Name)
   240  		service.PortLabel = taskEnv.ReplaceEnv(service.PortLabel)
   241  		service.Tags = taskEnv.ParseAndReplace(service.Tags)
   242  		service.CanaryTags = taskEnv.ParseAndReplace(service.CanaryTags)
   243  		interpolated[i] = service
   244  	}
   245  
   246  	return interpolated
   247  }