github.com/manicqin/nomad@v0.9.5/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  var _ interfaces.TaskPoststartHook = &serviceHook{}
    20  var _ interfaces.TaskPreKillHook = &serviceHook{}
    21  var _ interfaces.TaskExitedHook = &serviceHook{}
    22  var _ interfaces.TaskStopHook = &serviceHook{}
    23  
    24  type serviceHookConfig struct {
    25  	alloc  *structs.Allocation
    26  	task   *structs.Task
    27  	consul consul.ConsulServiceAPI
    28  
    29  	// Restarter is a subset of the TaskLifecycle interface
    30  	restarter agentconsul.WorkloadRestarter
    31  
    32  	logger log.Logger
    33  }
    34  
    35  type serviceHook struct {
    36  	consul    consul.ConsulServiceAPI
    37  	allocID   string
    38  	taskName  string
    39  	restarter agentconsul.WorkloadRestarter
    40  	logger    log.Logger
    41  
    42  	// The following fields may be updated
    43  	delay      time.Duration
    44  	driverExec tinterfaces.ScriptExecutor
    45  	driverNet  *drivers.DriverNetwork
    46  	canary     bool
    47  	services   []*structs.Service
    48  	networks   structs.Networks
    49  	taskEnv    *taskenv.TaskEnv
    50  
    51  	// Since Update() may be called concurrently with any other hook all
    52  	// hook methods must be fully serialized
    53  	mu sync.Mutex
    54  }
    55  
    56  func newServiceHook(c serviceHookConfig) *serviceHook {
    57  	h := &serviceHook{
    58  		consul:    c.consul,
    59  		allocID:   c.alloc.ID,
    60  		taskName:  c.task.Name,
    61  		services:  c.task.Services,
    62  		restarter: c.restarter,
    63  		delay:     c.task.ShutdownDelay,
    64  	}
    65  
    66  	// COMPAT(0.11): AllocatedResources was added in 0.9 so assume its set
    67  	//               in 0.11.
    68  	if c.alloc.AllocatedResources != nil {
    69  		if res := c.alloc.AllocatedResources.Tasks[c.task.Name]; res != nil {
    70  			h.networks = res.Networks
    71  		}
    72  	} else {
    73  		if res := c.alloc.TaskResources[c.task.Name]; res != nil {
    74  			h.networks = res.Networks
    75  		}
    76  	}
    77  
    78  	if c.alloc.DeploymentStatus != nil && c.alloc.DeploymentStatus.Canary {
    79  		h.canary = true
    80  	}
    81  
    82  	h.logger = c.logger.Named(h.Name())
    83  	return h
    84  }
    85  
    86  func (h *serviceHook) Name() string {
    87  	return "consul_services"
    88  }
    89  
    90  func (h *serviceHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, _ *interfaces.TaskPoststartResponse) error {
    91  	h.mu.Lock()
    92  	defer h.mu.Unlock()
    93  
    94  	// Store the TaskEnv for interpolating now and when Updating
    95  	h.driverExec = req.DriverExec
    96  	h.driverNet = req.DriverNetwork
    97  	h.taskEnv = req.TaskEnv
    98  
    99  	// Create task services struct with request's driver metadata
   100  	workloadServices := h.getWorkloadServices()
   101  
   102  	return h.consul.RegisterWorkload(workloadServices)
   103  }
   104  
   105  func (h *serviceHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, _ *interfaces.TaskUpdateResponse) error {
   106  	h.mu.Lock()
   107  	defer h.mu.Unlock()
   108  
   109  	// Create old task services struct with request's driver metadata as it
   110  	// can't change due to Updates
   111  	oldWorkloadServices := h.getWorkloadServices()
   112  
   113  	// Store new updated values out of request
   114  	canary := false
   115  	if req.Alloc.DeploymentStatus != nil {
   116  		canary = req.Alloc.DeploymentStatus.Canary
   117  	}
   118  
   119  	// COMPAT(0.11): AllocatedResources was added in 0.9 so assume its set
   120  	//               in 0.11.
   121  	var networks structs.Networks
   122  	if req.Alloc.AllocatedResources != nil {
   123  		if res := req.Alloc.AllocatedResources.Tasks[h.taskName]; res != nil {
   124  			networks = res.Networks
   125  		}
   126  	} else {
   127  		if res := req.Alloc.TaskResources[h.taskName]; res != nil {
   128  			networks = res.Networks
   129  		}
   130  	}
   131  
   132  	task := req.Alloc.LookupTask(h.taskName)
   133  	if task == nil {
   134  		return fmt.Errorf("task %q not found in updated alloc", h.taskName)
   135  	}
   136  
   137  	// Update service hook fields
   138  	h.delay = task.ShutdownDelay
   139  	h.taskEnv = req.TaskEnv
   140  	h.services = task.Services
   141  	h.networks = networks
   142  	h.canary = canary
   143  
   144  	// Create new task services struct with those new values
   145  	newWorkloadServices := h.getWorkloadServices()
   146  
   147  	return h.consul.UpdateWorkload(oldWorkloadServices, newWorkloadServices)
   148  }
   149  
   150  func (h *serviceHook) PreKilling(ctx context.Context, req *interfaces.TaskPreKillRequest, resp *interfaces.TaskPreKillResponse) error {
   151  	h.mu.Lock()
   152  	defer h.mu.Unlock()
   153  
   154  	// Deregister before killing task
   155  	h.deregister()
   156  
   157  	// If there's no shutdown delay, exit early
   158  	if h.delay == 0 {
   159  		return nil
   160  	}
   161  
   162  	h.logger.Debug("waiting before killing task", "shutdown_delay", h.delay)
   163  	select {
   164  	case <-ctx.Done():
   165  	case <-time.After(h.delay):
   166  	}
   167  	return nil
   168  }
   169  
   170  func (h *serviceHook) Exited(context.Context, *interfaces.TaskExitedRequest, *interfaces.TaskExitedResponse) error {
   171  	h.mu.Lock()
   172  	defer h.mu.Unlock()
   173  	h.deregister()
   174  	return nil
   175  }
   176  
   177  // deregister services from Consul.
   178  func (h *serviceHook) deregister() {
   179  	workloadServices := h.getWorkloadServices()
   180  	h.consul.RemoveWorkload(workloadServices)
   181  
   182  	// Canary flag may be getting flipped when the alloc is being
   183  	// destroyed, so remove both variations of the service
   184  	workloadServices.Canary = !workloadServices.Canary
   185  	h.consul.RemoveWorkload(workloadServices)
   186  
   187  }
   188  
   189  func (h *serviceHook) Stop(ctx context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error {
   190  	h.mu.Lock()
   191  	defer h.mu.Unlock()
   192  	h.deregister()
   193  	return nil
   194  }
   195  
   196  func (h *serviceHook) getWorkloadServices() *agentconsul.WorkloadServices {
   197  	// Interpolate with the task's environment
   198  	interpolatedServices := taskenv.InterpolateServices(h.taskEnv, h.services)
   199  
   200  	// Create task services struct with request's driver metadata
   201  	return &agentconsul.WorkloadServices{
   202  		AllocID:       h.allocID,
   203  		Task:          h.taskName,
   204  		Restarter:     h.restarter,
   205  		Services:      interpolatedServices,
   206  		DriverExec:    h.driverExec,
   207  		DriverNetwork: h.driverNet,
   208  		Networks:      h.networks,
   209  		Canary:        h.canary,
   210  	}
   211  }