github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/allocrunner/taskrunner/service_hook.go (about)

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