github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/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/serviceregistration"
    12  	"github.com/hashicorp/nomad/client/serviceregistration/wrapper"
    13  	"github.com/hashicorp/nomad/client/taskenv"
    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  var _ interfaces.TaskUpdateHook = &serviceHook{}
    23  
    24  const (
    25  	taskServiceHookName = "task_services"
    26  )
    27  
    28  type serviceHookConfig struct {
    29  	alloc *structs.Allocation
    30  	task  *structs.Task
    31  
    32  	// namespace is the Nomad or Consul namespace in which service
    33  	// registrations will be made.
    34  	providerNamespace string
    35  
    36  	// serviceRegWrapper is the handler wrapper that is used to perform service
    37  	// and check registration and deregistration.
    38  	serviceRegWrapper *wrapper.HandlerWrapper
    39  
    40  	// Restarter is a subset of the TaskLifecycle interface
    41  	restarter serviceregistration.WorkloadRestarter
    42  
    43  	logger log.Logger
    44  }
    45  
    46  type serviceHook struct {
    47  	allocID   string
    48  	jobID     string
    49  	groupName string
    50  	taskName  string
    51  	namespace string
    52  	restarter serviceregistration.WorkloadRestarter
    53  	logger    log.Logger
    54  
    55  	// The following fields may be updated
    56  	driverExec tinterfaces.ScriptExecutor
    57  	driverNet  *drivers.DriverNetwork
    58  	canary     bool
    59  	services   []*structs.Service
    60  	networks   structs.Networks
    61  	ports      structs.AllocatedPorts
    62  	taskEnv    *taskenv.TaskEnv
    63  
    64  	// providerNamespace is the Nomad or Consul namespace in which service
    65  	// registrations will be made. This field may be updated.
    66  	providerNamespace string
    67  
    68  	// serviceRegWrapper is the handler wrapper that is used to perform service
    69  	// and check registration and deregistration.
    70  	serviceRegWrapper *wrapper.HandlerWrapper
    71  
    72  	// initialRegistrations tracks if Poststart has completed, initializing
    73  	// fields required in other lifecycle funcs
    74  	initialRegistration bool
    75  
    76  	// deregistered tracks whether deregister() has previously been called, so
    77  	// we do not call this multiple times for a single task when not needed.
    78  	deregistered bool
    79  
    80  	// Since Update() may be called concurrently with any other hook all
    81  	// hook methods must be fully serialized
    82  	mu sync.Mutex
    83  }
    84  
    85  func newServiceHook(c serviceHookConfig) *serviceHook {
    86  	h := &serviceHook{
    87  		allocID:           c.alloc.ID,
    88  		jobID:             c.alloc.JobID,
    89  		groupName:         c.alloc.TaskGroup,
    90  		taskName:          c.task.Name,
    91  		namespace:         c.alloc.Namespace,
    92  		providerNamespace: c.providerNamespace,
    93  		serviceRegWrapper: c.serviceRegWrapper,
    94  		services:          c.task.Services,
    95  		restarter:         c.restarter,
    96  		ports:             c.alloc.AllocatedResources.Shared.Ports,
    97  	}
    98  
    99  	if res := c.alloc.AllocatedResources.Tasks[c.task.Name]; res != nil {
   100  		h.networks = res.Networks
   101  	}
   102  
   103  	if c.alloc.DeploymentStatus != nil && c.alloc.DeploymentStatus.Canary {
   104  		h.canary = true
   105  	}
   106  
   107  	h.logger = c.logger.Named(h.Name())
   108  	return h
   109  }
   110  
   111  func (h *serviceHook) Name() string { return taskServiceHookName }
   112  
   113  func (h *serviceHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, _ *interfaces.TaskPoststartResponse) error {
   114  	h.mu.Lock()
   115  	defer h.mu.Unlock()
   116  
   117  	// Store the TaskEnv for interpolating now and when Updating
   118  	h.driverExec = req.DriverExec
   119  	h.driverNet = req.DriverNetwork
   120  	h.taskEnv = req.TaskEnv
   121  	h.initialRegistration = true
   122  
   123  	// Ensure deregistered is unset.
   124  	h.deregistered = false
   125  
   126  	// Create task services struct with request's driver metadata
   127  	workloadServices := h.getWorkloadServices()
   128  
   129  	return h.serviceRegWrapper.RegisterWorkload(workloadServices)
   130  }
   131  
   132  func (h *serviceHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, _ *interfaces.TaskUpdateResponse) error {
   133  	h.mu.Lock()
   134  	defer h.mu.Unlock()
   135  	if !h.initialRegistration {
   136  		// no op since initial registration has not finished only update hook
   137  		// fields.
   138  		return h.updateHookFields(req)
   139  	}
   140  
   141  	// Create old task services struct with request's driver metadata as it
   142  	// can't change due to Updates
   143  	oldWorkloadServices := h.getWorkloadServices()
   144  
   145  	if err := h.updateHookFields(req); err != nil {
   146  		return err
   147  	}
   148  
   149  	// Create new task services struct with those new values
   150  	newWorkloadServices := h.getWorkloadServices()
   151  
   152  	return h.serviceRegWrapper.UpdateWorkload(oldWorkloadServices, newWorkloadServices)
   153  }
   154  
   155  func (h *serviceHook) updateHookFields(req *interfaces.TaskUpdateRequest) error {
   156  	// Store new updated values out of request
   157  	canary := false
   158  	if req.Alloc.DeploymentStatus != nil {
   159  		canary = req.Alloc.DeploymentStatus.Canary
   160  	}
   161  
   162  	var networks structs.Networks
   163  	if res := req.Alloc.AllocatedResources.Tasks[h.taskName]; res != nil {
   164  		networks = res.Networks
   165  	}
   166  
   167  	task := req.Alloc.LookupTask(h.taskName)
   168  	if task == nil {
   169  		return fmt.Errorf("task %q not found in updated alloc", h.taskName)
   170  	}
   171  
   172  	// Update service hook fields
   173  	h.taskEnv = req.TaskEnv
   174  	h.services = task.Services
   175  	h.networks = networks
   176  	h.canary = canary
   177  	h.ports = req.Alloc.AllocatedResources.Shared.Ports
   178  
   179  	// An update may change the service provider, therefore we need to account
   180  	// for how namespaces work across providers also.
   181  	h.providerNamespace = req.Alloc.ServiceProviderNamespace()
   182  
   183  	return nil
   184  }
   185  
   186  func (h *serviceHook) PreKilling(ctx context.Context, req *interfaces.TaskPreKillRequest, resp *interfaces.TaskPreKillResponse) error {
   187  	h.mu.Lock()
   188  	defer h.mu.Unlock()
   189  
   190  	// Deregister before killing task
   191  	h.deregister()
   192  
   193  	return nil
   194  }
   195  
   196  func (h *serviceHook) Exited(context.Context, *interfaces.TaskExitedRequest, *interfaces.TaskExitedResponse) error {
   197  	h.mu.Lock()
   198  	defer h.mu.Unlock()
   199  	h.deregister()
   200  	return nil
   201  }
   202  
   203  // deregister services from Consul.
   204  func (h *serviceHook) deregister() {
   205  	if len(h.services) > 0 && !h.deregistered {
   206  		workloadServices := h.getWorkloadServices()
   207  		h.serviceRegWrapper.RemoveWorkload(workloadServices)
   208  	}
   209  	h.initialRegistration = false
   210  	h.deregistered = true
   211  }
   212  
   213  func (h *serviceHook) Stop(ctx context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error {
   214  	h.mu.Lock()
   215  	defer h.mu.Unlock()
   216  	h.deregister()
   217  	return nil
   218  }
   219  
   220  func (h *serviceHook) getWorkloadServices() *serviceregistration.WorkloadServices {
   221  	// Interpolate with the task's environment
   222  	interpolatedServices := taskenv.InterpolateServices(h.taskEnv, h.services)
   223  
   224  	info := structs.AllocInfo{
   225  		AllocID:   h.allocID,
   226  		JobID:     h.jobID,
   227  		Task:      h.taskName,
   228  		Namespace: h.namespace,
   229  	}
   230  
   231  	// Create task services struct with request's driver metadata
   232  	return &serviceregistration.WorkloadServices{
   233  		AllocInfo:         info,
   234  		ProviderNamespace: h.providerNamespace,
   235  		Restarter:         h.restarter,
   236  		Services:          interpolatedServices,
   237  		DriverExec:        h.driverExec,
   238  		DriverNetwork:     h.driverNet,
   239  		Networks:          h.networks,
   240  		Canary:            h.canary,
   241  		Ports:             h.ports,
   242  	}
   243  }