github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/allocrunner/groupservice_hook.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	log "github.com/hashicorp/go-hclog"
     8  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
     9  	"github.com/hashicorp/nomad/client/consul"
    10  	"github.com/hashicorp/nomad/client/taskenv"
    11  	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/hashicorp/nomad/plugins/drivers"
    14  )
    15  
    16  // groupServiceHook manages task group Consul service registration and
    17  // deregistration.
    18  type groupServiceHook struct {
    19  	allocID      string
    20  	group        string
    21  	restarter    agentconsul.WorkloadRestarter
    22  	consulClient consul.ConsulServiceAPI
    23  	prerun       bool
    24  	delay        time.Duration
    25  	deregistered bool
    26  
    27  	logger log.Logger
    28  
    29  	// The following fields may be updated
    30  	canary         bool
    31  	services       []*structs.Service
    32  	networks       structs.Networks
    33  	taskEnvBuilder *taskenv.Builder
    34  
    35  	// Since Update() may be called concurrently with any other hook all
    36  	// hook methods must be fully serialized
    37  	mu sync.Mutex
    38  }
    39  
    40  type groupServiceHookConfig struct {
    41  	alloc          *structs.Allocation
    42  	consul         consul.ConsulServiceAPI
    43  	restarter      agentconsul.WorkloadRestarter
    44  	taskEnvBuilder *taskenv.Builder
    45  	logger         log.Logger
    46  }
    47  
    48  func newGroupServiceHook(cfg groupServiceHookConfig) *groupServiceHook {
    49  	var shutdownDelay time.Duration
    50  	tg := cfg.alloc.Job.LookupTaskGroup(cfg.alloc.TaskGroup)
    51  
    52  	if tg.ShutdownDelay != nil {
    53  		shutdownDelay = *tg.ShutdownDelay
    54  	}
    55  
    56  	h := &groupServiceHook{
    57  		allocID:        cfg.alloc.ID,
    58  		group:          cfg.alloc.TaskGroup,
    59  		restarter:      cfg.restarter,
    60  		consulClient:   cfg.consul,
    61  		taskEnvBuilder: cfg.taskEnvBuilder,
    62  		delay:          shutdownDelay,
    63  	}
    64  	h.logger = cfg.logger.Named(h.Name())
    65  	h.services = cfg.alloc.Job.LookupTaskGroup(h.group).Services
    66  
    67  	if cfg.alloc.AllocatedResources != nil {
    68  		h.networks = cfg.alloc.AllocatedResources.Shared.Networks
    69  	}
    70  
    71  	if cfg.alloc.DeploymentStatus != nil {
    72  		h.canary = cfg.alloc.DeploymentStatus.Canary
    73  	}
    74  	return h
    75  }
    76  
    77  func (*groupServiceHook) Name() string {
    78  	return "group_services"
    79  }
    80  
    81  func (h *groupServiceHook) Prerun() error {
    82  	h.mu.Lock()
    83  	defer func() {
    84  		// Mark prerun as true to unblock Updates
    85  		h.prerun = true
    86  		h.mu.Unlock()
    87  	}()
    88  
    89  	if len(h.services) == 0 {
    90  		return nil
    91  	}
    92  
    93  	services := h.getWorkloadServices()
    94  	return h.consulClient.RegisterWorkload(services)
    95  }
    96  
    97  func (h *groupServiceHook) Update(req *interfaces.RunnerUpdateRequest) error {
    98  	h.mu.Lock()
    99  	defer h.mu.Unlock()
   100  
   101  	oldWorkloadServices := h.getWorkloadServices()
   102  
   103  	// Store new updated values out of request
   104  	canary := false
   105  	if req.Alloc.DeploymentStatus != nil {
   106  		canary = req.Alloc.DeploymentStatus.Canary
   107  	}
   108  
   109  	var networks structs.Networks
   110  	if req.Alloc.AllocatedResources != nil {
   111  		networks = req.Alloc.AllocatedResources.Shared.Networks
   112  	}
   113  
   114  	tg := req.Alloc.Job.LookupTaskGroup(h.group)
   115  	var shutdown time.Duration
   116  	if tg.ShutdownDelay != nil {
   117  		shutdown = *tg.ShutdownDelay
   118  	}
   119  
   120  	// Update group service hook fields
   121  	h.networks = networks
   122  	h.services = tg.Services
   123  	h.canary = canary
   124  	h.delay = shutdown
   125  	h.taskEnvBuilder.UpdateTask(req.Alloc, nil)
   126  
   127  	// Create new task services struct with those new values
   128  	newWorkloadServices := h.getWorkloadServices()
   129  
   130  	if !h.prerun {
   131  		// Update called before Prerun. Update alloc and exit to allow
   132  		// Prerun to do initial registration.
   133  		return nil
   134  	}
   135  
   136  	return h.consulClient.UpdateWorkload(oldWorkloadServices, newWorkloadServices)
   137  }
   138  
   139  func (h *groupServiceHook) PreKill() {
   140  	h.mu.Lock()
   141  	defer h.mu.Unlock()
   142  
   143  	// If we have a shutdown delay deregister
   144  	// group services and then wait
   145  	// before continuing to kill tasks
   146  	h.deregister()
   147  	h.deregistered = true
   148  
   149  	if h.delay == 0 {
   150  		return
   151  	}
   152  
   153  	h.logger.Debug("waiting before removing group service", "shutdown_delay", h.delay)
   154  
   155  	// Wait for specified shutdown_delay
   156  	// this will block an agent from shutting down
   157  	<-time.After(h.delay)
   158  }
   159  
   160  func (h *groupServiceHook) Postrun() error {
   161  	h.mu.Lock()
   162  	defer h.mu.Unlock()
   163  
   164  	if !h.deregistered {
   165  		h.deregister()
   166  	}
   167  	return nil
   168  }
   169  
   170  func (h *groupServiceHook) driverNet() *drivers.DriverNetwork {
   171  	if len(h.networks) == 0 {
   172  		return nil
   173  	}
   174  
   175  	//TODO(schmichael) only support one network for now
   176  	net := h.networks[0]
   177  	//TODO(schmichael) there's probably a better way than hacking driver network
   178  	return &drivers.DriverNetwork{
   179  		AutoAdvertise: true,
   180  		IP:            net.IP,
   181  		// Copy PortLabels from group network
   182  		PortMap: net.PortLabels(),
   183  	}
   184  }
   185  
   186  // deregister services from Consul.
   187  func (h *groupServiceHook) deregister() {
   188  	if len(h.services) > 0 {
   189  		workloadServices := h.getWorkloadServices()
   190  		h.consulClient.RemoveWorkload(workloadServices)
   191  
   192  		// Canary flag may be getting flipped when the alloc is being
   193  		// destroyed, so remove both variations of the service
   194  		workloadServices.Canary = !workloadServices.Canary
   195  		h.consulClient.RemoveWorkload(workloadServices)
   196  	}
   197  }
   198  
   199  func (h *groupServiceHook) getWorkloadServices() *agentconsul.WorkloadServices {
   200  	// Interpolate with the task's environment
   201  	interpolatedServices := taskenv.InterpolateServices(h.taskEnvBuilder.Build(), h.services)
   202  
   203  	// Create task services struct with request's driver metadata
   204  	return &agentconsul.WorkloadServices{
   205  		AllocID:       h.allocID,
   206  		Group:         h.group,
   207  		Restarter:     h.restarter,
   208  		Services:      interpolatedServices,
   209  		DriverNetwork: h.driverNet(),
   210  		Networks:      h.networks,
   211  		Canary:        h.canary,
   212  	}
   213  }