github.com/bigcommerce/nomad@v0.9.3-bc/client/allocrunner/alloc_runner_hooks.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	multierror "github.com/hashicorp/go-multierror"
     8  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  )
    11  
    12  // allocHealthSetter is a shim to allow the alloc health watcher hook to set
    13  // and clear the alloc health without full access to the alloc runner state
    14  type allocHealthSetter struct {
    15  	ar *allocRunner
    16  }
    17  
    18  // HasHealth returns true if a deployment status is already set.
    19  func (a *allocHealthSetter) HasHealth() bool {
    20  	a.ar.stateLock.Lock()
    21  	defer a.ar.stateLock.Unlock()
    22  	return a.ar.state.DeploymentStatus.HasHealth()
    23  }
    24  
    25  // ClearHealth allows the health watcher hook to clear the alloc's deployment
    26  // health if the deployment id changes. It does not update the server as the
    27  // status is only cleared when already receiving an update from the server.
    28  //
    29  // Only for use by health hook.
    30  func (a *allocHealthSetter) ClearHealth() {
    31  	a.ar.stateLock.Lock()
    32  	a.ar.state.ClearDeploymentStatus()
    33  	a.ar.persistDeploymentStatus(nil)
    34  	a.ar.stateLock.Unlock()
    35  }
    36  
    37  // SetHealth allows the health watcher hook to set the alloc's
    38  // deployment/migration health and emit task events.
    39  //
    40  // Only for use by health hook.
    41  func (a *allocHealthSetter) SetHealth(healthy, isDeploy bool, trackerTaskEvents map[string]*structs.TaskEvent) {
    42  	// Updating alloc deployment state is tricky because it may be nil, but
    43  	// if it's not then we need to maintain the values of Canary and
    44  	// ModifyIndex as they're only mutated by the server.
    45  	a.ar.stateLock.Lock()
    46  	a.ar.state.SetDeploymentStatus(time.Now(), healthy)
    47  	a.ar.persistDeploymentStatus(a.ar.state.DeploymentStatus)
    48  	terminalDesiredState := a.ar.Alloc().ServerTerminalStatus()
    49  	a.ar.stateLock.Unlock()
    50  
    51  	// If deployment is unhealthy emit task events explaining why
    52  	if !healthy && isDeploy && !terminalDesiredState {
    53  		for task, event := range trackerTaskEvents {
    54  			if tr, ok := a.ar.tasks[task]; ok {
    55  				// Append but don't emit event since the server
    56  				// will be updated below
    57  				tr.AppendEvent(event)
    58  			}
    59  		}
    60  	}
    61  
    62  	// Gather the state of the other tasks
    63  	states := make(map[string]*structs.TaskState, len(a.ar.tasks))
    64  	for name, tr := range a.ar.tasks {
    65  		states[name] = tr.TaskState()
    66  	}
    67  
    68  	// Build the client allocation
    69  	calloc := a.ar.clientAlloc(states)
    70  
    71  	// Update the server
    72  	a.ar.stateUpdater.AllocStateUpdated(calloc)
    73  
    74  	// Broadcast client alloc to listeners
    75  	a.ar.allocBroadcaster.Send(calloc)
    76  }
    77  
    78  // initRunnerHooks intializes the runners hooks.
    79  func (ar *allocRunner) initRunnerHooks() {
    80  	hookLogger := ar.logger.Named("runner_hook")
    81  
    82  	// create health setting shim
    83  	hs := &allocHealthSetter{ar}
    84  
    85  	// Create the alloc directory hook. This is run first to ensure the
    86  	// directory path exists for other hooks.
    87  	ar.runnerHooks = []interfaces.RunnerHook{
    88  		newAllocDirHook(hookLogger, ar.allocDir),
    89  		newUpstreamAllocsHook(hookLogger, ar.prevAllocWatcher),
    90  		newDiskMigrationHook(hookLogger, ar.prevAllocMigrator, ar.allocDir),
    91  		newAllocHealthWatcherHook(hookLogger, ar.Alloc(), hs, ar.Listener(), ar.consulClient),
    92  	}
    93  }
    94  
    95  // prerun is used to run the runners prerun hooks.
    96  func (ar *allocRunner) prerun() error {
    97  	if ar.logger.IsTrace() {
    98  		start := time.Now()
    99  		ar.logger.Trace("running pre-run hooks", "start", start)
   100  		defer func() {
   101  			end := time.Now()
   102  			ar.logger.Trace("finished pre-run hooks", "end", end, "duration", end.Sub(start))
   103  		}()
   104  	}
   105  
   106  	for _, hook := range ar.runnerHooks {
   107  		pre, ok := hook.(interfaces.RunnerPrerunHook)
   108  		if !ok {
   109  			continue
   110  		}
   111  
   112  		name := pre.Name()
   113  		var start time.Time
   114  		if ar.logger.IsTrace() {
   115  			start = time.Now()
   116  			ar.logger.Trace("running pre-run hook", "name", name, "start", start)
   117  		}
   118  
   119  		if err := pre.Prerun(); err != nil {
   120  			return fmt.Errorf("pre-run hook %q failed: %v", name, err)
   121  		}
   122  
   123  		if ar.logger.IsTrace() {
   124  			end := time.Now()
   125  			ar.logger.Trace("finished pre-run hook", "name", name, "end", end, "duration", end.Sub(start))
   126  		}
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  // update runs the alloc runner update hooks. Update hooks are run
   133  // asynchronously with all other alloc runner operations.
   134  func (ar *allocRunner) update(update *structs.Allocation) error {
   135  	if ar.logger.IsTrace() {
   136  		start := time.Now()
   137  		ar.logger.Trace("running update hooks", "start", start)
   138  		defer func() {
   139  			end := time.Now()
   140  			ar.logger.Trace("finished update hooks", "end", end, "duration", end.Sub(start))
   141  		}()
   142  	}
   143  
   144  	req := &interfaces.RunnerUpdateRequest{
   145  		Alloc: update,
   146  	}
   147  
   148  	var merr multierror.Error
   149  	for _, hook := range ar.runnerHooks {
   150  		h, ok := hook.(interfaces.RunnerUpdateHook)
   151  		if !ok {
   152  			continue
   153  		}
   154  
   155  		name := h.Name()
   156  		var start time.Time
   157  		if ar.logger.IsTrace() {
   158  			start = time.Now()
   159  			ar.logger.Trace("running pre-run hook", "name", name, "start", start)
   160  		}
   161  
   162  		if err := h.Update(req); err != nil {
   163  			merr.Errors = append(merr.Errors, fmt.Errorf("update hook %q failed: %v", name, err))
   164  		}
   165  
   166  		if ar.logger.IsTrace() {
   167  			end := time.Now()
   168  			ar.logger.Trace("finished update hooks", "name", name, "end", end, "duration", end.Sub(start))
   169  		}
   170  	}
   171  
   172  	return merr.ErrorOrNil()
   173  }
   174  
   175  // postrun is used to run the runners postrun hooks.
   176  func (ar *allocRunner) postrun() error {
   177  	if ar.logger.IsTrace() {
   178  		start := time.Now()
   179  		ar.logger.Trace("running post-run hooks", "start", start)
   180  		defer func() {
   181  			end := time.Now()
   182  			ar.logger.Trace("finished post-run hooks", "end", end, "duration", end.Sub(start))
   183  		}()
   184  	}
   185  
   186  	for _, hook := range ar.runnerHooks {
   187  		post, ok := hook.(interfaces.RunnerPostrunHook)
   188  		if !ok {
   189  			continue
   190  		}
   191  
   192  		name := post.Name()
   193  		var start time.Time
   194  		if ar.logger.IsTrace() {
   195  			start = time.Now()
   196  			ar.logger.Trace("running post-run hook", "name", name, "start", start)
   197  		}
   198  
   199  		if err := post.Postrun(); err != nil {
   200  			return fmt.Errorf("hook %q failed: %v", name, err)
   201  		}
   202  
   203  		if ar.logger.IsTrace() {
   204  			end := time.Now()
   205  			ar.logger.Trace("finished post-run hooks", "name", name, "end", end, "duration", end.Sub(start))
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  // destroy is used to run the runners destroy hooks. All hooks are run and
   213  // errors are returned as a multierror.
   214  func (ar *allocRunner) destroy() error {
   215  	if ar.logger.IsTrace() {
   216  		start := time.Now()
   217  		ar.logger.Trace("running destroy hooks", "start", start)
   218  		defer func() {
   219  			end := time.Now()
   220  			ar.logger.Trace("finished destroy hooks", "end", end, "duration", end.Sub(start))
   221  		}()
   222  	}
   223  
   224  	var merr multierror.Error
   225  	for _, hook := range ar.runnerHooks {
   226  		h, ok := hook.(interfaces.RunnerDestroyHook)
   227  		if !ok {
   228  			continue
   229  		}
   230  
   231  		name := h.Name()
   232  		var start time.Time
   233  		if ar.logger.IsTrace() {
   234  			start = time.Now()
   235  			ar.logger.Trace("running destroy hook", "name", name, "start", start)
   236  		}
   237  
   238  		if err := h.Destroy(); err != nil {
   239  			merr.Errors = append(merr.Errors, fmt.Errorf("destroy hook %q failed: %v", name, err))
   240  		}
   241  
   242  		if ar.logger.IsTrace() {
   243  			end := time.Now()
   244  			ar.logger.Trace("finished destroy hooks", "name", name, "end", end, "duration", end.Sub(start))
   245  		}
   246  	}
   247  
   248  	return merr.ErrorOrNil()
   249  }
   250  
   251  // shutdownHooks calls graceful shutdown hooks for when the agent is exiting.
   252  func (ar *allocRunner) shutdownHooks() {
   253  	for _, hook := range ar.runnerHooks {
   254  		sh, ok := hook.(interfaces.ShutdownHook)
   255  		if !ok {
   256  			continue
   257  		}
   258  
   259  		name := sh.Name()
   260  		var start time.Time
   261  		if ar.logger.IsTrace() {
   262  			start = time.Now()
   263  			ar.logger.Trace("running shutdown hook", "name", name, "start", start)
   264  		}
   265  
   266  		sh.Shutdown()
   267  
   268  		if ar.logger.IsTrace() {
   269  			end := time.Now()
   270  			ar.logger.Trace("finished shutdown hooks", "name", name, "end", end, "duration", end.Sub(start))
   271  		}
   272  	}
   273  }