github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  	clientconfig "github.com/hashicorp/nomad/client/config"
    10  	cstructs "github.com/hashicorp/nomad/client/structs"
    11  	"github.com/hashicorp/nomad/client/taskenv"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  type hookResourceSetter interface {
    16  	GetAllocHookResources() *cstructs.AllocHookResources
    17  	SetAllocHookResources(*cstructs.AllocHookResources)
    18  }
    19  
    20  type allocHookResourceSetter struct {
    21  	ar *allocRunner
    22  }
    23  
    24  func (a *allocHookResourceSetter) GetAllocHookResources() *cstructs.AllocHookResources {
    25  	a.ar.hookStateMu.RLock()
    26  	defer a.ar.hookStateMu.RUnlock()
    27  
    28  	return a.ar.hookState
    29  }
    30  
    31  func (a *allocHookResourceSetter) SetAllocHookResources(res *cstructs.AllocHookResources) {
    32  	a.ar.hookStateMu.Lock()
    33  	defer a.ar.hookStateMu.Unlock()
    34  
    35  	a.ar.hookState = res
    36  
    37  	// Propagate to all of the TRs within the lock to ensure consistent state.
    38  	// TODO: Refactor so TR's pull state from AR?
    39  	for _, tr := range a.ar.tasks {
    40  		tr.SetAllocHookResources(res)
    41  	}
    42  }
    43  
    44  // allocHealthSetter is a shim to allow the alloc health watcher hook to set
    45  // and clear the alloc health without full access to the alloc runner state
    46  type allocHealthSetter struct {
    47  	ar *allocRunner
    48  }
    49  
    50  // HasHealth returns true if a deployment status is already set.
    51  func (a *allocHealthSetter) HasHealth() bool {
    52  	a.ar.stateLock.Lock()
    53  	defer a.ar.stateLock.Unlock()
    54  	return a.ar.state.DeploymentStatus.HasHealth()
    55  }
    56  
    57  // ClearHealth allows the health watcher hook to clear the alloc's deployment
    58  // health if the deployment id changes. It does not update the server as the
    59  // status is only cleared when already receiving an update from the server.
    60  //
    61  // Only for use by health hook.
    62  func (a *allocHealthSetter) ClearHealth() {
    63  	a.ar.stateLock.Lock()
    64  	a.ar.state.ClearDeploymentStatus()
    65  	a.ar.persistDeploymentStatus(nil)
    66  	a.ar.stateLock.Unlock()
    67  }
    68  
    69  // SetHealth allows the health watcher hook to set the alloc's
    70  // deployment/migration health and emit task events.
    71  //
    72  // Only for use by health hook.
    73  func (a *allocHealthSetter) SetHealth(healthy, isDeploy bool, trackerTaskEvents map[string]*structs.TaskEvent) {
    74  	// Updating alloc deployment state is tricky because it may be nil, but
    75  	// if it's not then we need to maintain the values of Canary and
    76  	// ModifyIndex as they're only mutated by the server.
    77  	a.ar.stateLock.Lock()
    78  	a.ar.state.SetDeploymentStatus(time.Now(), healthy)
    79  	a.ar.persistDeploymentStatus(a.ar.state.DeploymentStatus)
    80  	terminalDesiredState := a.ar.Alloc().ServerTerminalStatus()
    81  	a.ar.stateLock.Unlock()
    82  
    83  	// If deployment is unhealthy emit task events explaining why
    84  	if !healthy && isDeploy && !terminalDesiredState {
    85  		for task, event := range trackerTaskEvents {
    86  			if tr, ok := a.ar.tasks[task]; ok {
    87  				// Append but don't emit event since the server
    88  				// will be updated below
    89  				tr.AppendEvent(event)
    90  			}
    91  		}
    92  	}
    93  
    94  	// Gather the state of the other tasks
    95  	states := make(map[string]*structs.TaskState, len(a.ar.tasks))
    96  	for name, tr := range a.ar.tasks {
    97  		states[name] = tr.TaskState()
    98  	}
    99  
   100  	// Build the client allocation
   101  	calloc := a.ar.clientAlloc(states)
   102  
   103  	// Update the server
   104  	a.ar.stateUpdater.AllocStateUpdated(calloc)
   105  
   106  	// Broadcast client alloc to listeners
   107  	a.ar.allocBroadcaster.Send(calloc)
   108  }
   109  
   110  // initRunnerHooks initializes the runners hooks.
   111  func (ar *allocRunner) initRunnerHooks(config *clientconfig.Config) error {
   112  	hookLogger := ar.logger.Named("runner_hook")
   113  
   114  	// create health setting shim
   115  	hs := &allocHealthSetter{ar}
   116  
   117  	// create network isolation setting shim
   118  	ns := &allocNetworkIsolationSetter{ar: ar}
   119  
   120  	// create hook resource setting shim
   121  	hrs := &allocHookResourceSetter{ar: ar}
   122  	hrs.SetAllocHookResources(&cstructs.AllocHookResources{})
   123  
   124  	// build the network manager
   125  	nm, err := newNetworkManager(ar.Alloc(), ar.driverManager)
   126  	if err != nil {
   127  		return fmt.Errorf("failed to configure network manager: %v", err)
   128  	}
   129  
   130  	// create network configurator
   131  	nc, err := newNetworkConfigurator(hookLogger, ar.Alloc(), config)
   132  	if err != nil {
   133  		return fmt.Errorf("failed to initialize network configurator: %v", err)
   134  	}
   135  
   136  	// Create the alloc directory hook. This is run first to ensure the
   137  	// directory path exists for other hooks.
   138  	alloc := ar.Alloc()
   139  	ar.runnerHooks = []interfaces.RunnerHook{
   140  		newAllocDirHook(hookLogger, ar.allocDir),
   141  		newUpstreamAllocsHook(hookLogger, ar.prevAllocWatcher),
   142  		newDiskMigrationHook(hookLogger, ar.prevAllocMigrator, ar.allocDir),
   143  		newAllocHealthWatcherHook(hookLogger, alloc, hs, ar.Listener(), ar.consulClient),
   144  		newNetworkHook(hookLogger, ns, alloc, nm, nc, ar),
   145  		newGroupServiceHook(groupServiceHookConfig{
   146  			alloc:               alloc,
   147  			consul:              ar.consulClient,
   148  			restarter:           ar,
   149  			taskEnvBuilder:      taskenv.NewBuilder(config.Node, ar.Alloc(), nil, config.Region).SetAllocDir(ar.allocDir.AllocDir),
   150  			networkStatusGetter: ar,
   151  			logger:              hookLogger,
   152  		}),
   153  		newConsulGRPCSocketHook(hookLogger, alloc, ar.allocDir, config.ConsulConfig),
   154  		newConsulHTTPSocketHook(hookLogger, alloc, ar.allocDir, config.ConsulConfig),
   155  		newCSIHook(ar, hookLogger, alloc, ar.rpcClient, ar.csiManager, hrs),
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // prerun is used to run the runners prerun hooks.
   162  func (ar *allocRunner) prerun() error {
   163  	if ar.logger.IsTrace() {
   164  		start := time.Now()
   165  		ar.logger.Trace("running pre-run hooks", "start", start)
   166  		defer func() {
   167  			end := time.Now()
   168  			ar.logger.Trace("finished pre-run hooks", "end", end, "duration", end.Sub(start))
   169  		}()
   170  	}
   171  
   172  	for _, hook := range ar.runnerHooks {
   173  		pre, ok := hook.(interfaces.RunnerPrerunHook)
   174  		if !ok {
   175  			continue
   176  		}
   177  
   178  		name := pre.Name()
   179  		var start time.Time
   180  		if ar.logger.IsTrace() {
   181  			start = time.Now()
   182  			ar.logger.Trace("running pre-run hook", "name", name, "start", start)
   183  		}
   184  
   185  		if err := pre.Prerun(); err != nil {
   186  			return fmt.Errorf("pre-run hook %q failed: %v", name, err)
   187  		}
   188  
   189  		if ar.logger.IsTrace() {
   190  			end := time.Now()
   191  			ar.logger.Trace("finished pre-run hook", "name", name, "end", end, "duration", end.Sub(start))
   192  		}
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  // update runs the alloc runner update hooks. Update hooks are run
   199  // asynchronously with all other alloc runner operations.
   200  func (ar *allocRunner) update(update *structs.Allocation) error {
   201  	if ar.logger.IsTrace() {
   202  		start := time.Now()
   203  		ar.logger.Trace("running update hooks", "start", start)
   204  		defer func() {
   205  			end := time.Now()
   206  			ar.logger.Trace("finished update hooks", "end", end, "duration", end.Sub(start))
   207  		}()
   208  	}
   209  
   210  	req := &interfaces.RunnerUpdateRequest{
   211  		Alloc: update,
   212  	}
   213  
   214  	var merr multierror.Error
   215  	for _, hook := range ar.runnerHooks {
   216  		h, ok := hook.(interfaces.RunnerUpdateHook)
   217  		if !ok {
   218  			continue
   219  		}
   220  
   221  		name := h.Name()
   222  		var start time.Time
   223  		if ar.logger.IsTrace() {
   224  			start = time.Now()
   225  			ar.logger.Trace("running update hook", "name", name, "start", start)
   226  		}
   227  
   228  		if err := h.Update(req); err != nil {
   229  			merr.Errors = append(merr.Errors, fmt.Errorf("update hook %q failed: %v", name, err))
   230  		}
   231  
   232  		if ar.logger.IsTrace() {
   233  			end := time.Now()
   234  			ar.logger.Trace("finished update hooks", "name", name, "end", end, "duration", end.Sub(start))
   235  		}
   236  	}
   237  
   238  	return merr.ErrorOrNil()
   239  }
   240  
   241  // postrun is used to run the runners postrun hooks.
   242  func (ar *allocRunner) postrun() error {
   243  	if ar.logger.IsTrace() {
   244  		start := time.Now()
   245  		ar.logger.Trace("running post-run hooks", "start", start)
   246  		defer func() {
   247  			end := time.Now()
   248  			ar.logger.Trace("finished post-run hooks", "end", end, "duration", end.Sub(start))
   249  		}()
   250  	}
   251  
   252  	for _, hook := range ar.runnerHooks {
   253  		post, ok := hook.(interfaces.RunnerPostrunHook)
   254  		if !ok {
   255  			continue
   256  		}
   257  
   258  		name := post.Name()
   259  		var start time.Time
   260  		if ar.logger.IsTrace() {
   261  			start = time.Now()
   262  			ar.logger.Trace("running post-run hook", "name", name, "start", start)
   263  		}
   264  
   265  		if err := post.Postrun(); err != nil {
   266  			return fmt.Errorf("hook %q failed: %v", name, err)
   267  		}
   268  
   269  		if ar.logger.IsTrace() {
   270  			end := time.Now()
   271  			ar.logger.Trace("finished post-run hooks", "name", name, "end", end, "duration", end.Sub(start))
   272  		}
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  // destroy is used to run the runners destroy hooks. All hooks are run and
   279  // errors are returned as a multierror.
   280  func (ar *allocRunner) destroy() error {
   281  	if ar.logger.IsTrace() {
   282  		start := time.Now()
   283  		ar.logger.Trace("running destroy hooks", "start", start)
   284  		defer func() {
   285  			end := time.Now()
   286  			ar.logger.Trace("finished destroy hooks", "end", end, "duration", end.Sub(start))
   287  		}()
   288  	}
   289  
   290  	var merr multierror.Error
   291  	for _, hook := range ar.runnerHooks {
   292  		h, ok := hook.(interfaces.RunnerDestroyHook)
   293  		if !ok {
   294  			continue
   295  		}
   296  
   297  		name := h.Name()
   298  		var start time.Time
   299  		if ar.logger.IsTrace() {
   300  			start = time.Now()
   301  			ar.logger.Trace("running destroy hook", "name", name, "start", start)
   302  		}
   303  
   304  		if err := h.Destroy(); err != nil {
   305  			merr.Errors = append(merr.Errors, fmt.Errorf("destroy hook %q failed: %v", name, err))
   306  		}
   307  
   308  		if ar.logger.IsTrace() {
   309  			end := time.Now()
   310  			ar.logger.Trace("finished destroy hooks", "name", name, "end", end, "duration", end.Sub(start))
   311  		}
   312  	}
   313  
   314  	return merr.ErrorOrNil()
   315  }
   316  
   317  func (ar *allocRunner) preKillHooks() {
   318  	for _, hook := range ar.runnerHooks {
   319  		pre, ok := hook.(interfaces.RunnerPreKillHook)
   320  		if !ok {
   321  			continue
   322  		}
   323  
   324  		name := pre.Name()
   325  		var start time.Time
   326  		if ar.logger.IsTrace() {
   327  			start = time.Now()
   328  			ar.logger.Trace("running alloc pre shutdown hook", "name", name, "start", start)
   329  		}
   330  
   331  		pre.PreKill()
   332  
   333  		if ar.logger.IsTrace() {
   334  			end := time.Now()
   335  			ar.logger.Trace("finished alloc pre shutdown hook", "name", name, "end", end, "duration", end.Sub(start))
   336  		}
   337  	}
   338  }
   339  
   340  // shutdownHooks calls graceful shutdown hooks for when the agent is exiting.
   341  func (ar *allocRunner) shutdownHooks() {
   342  	for _, hook := range ar.runnerHooks {
   343  		sh, ok := hook.(interfaces.ShutdownHook)
   344  		if !ok {
   345  			continue
   346  		}
   347  
   348  		name := sh.Name()
   349  		var start time.Time
   350  		if ar.logger.IsTrace() {
   351  			start = time.Now()
   352  			ar.logger.Trace("running shutdown hook", "name", name, "start", start)
   353  		}
   354  
   355  		sh.Shutdown()
   356  
   357  		if ar.logger.IsTrace() {
   358  			end := time.Now()
   359  			ar.logger.Trace("finished shutdown hooks", "name", name, "end", end, "duration", end.Sub(start))
   360  		}
   361  	}
   362  }
   363  
   364  func (ar *allocRunner) taskRestartHooks() {
   365  	for _, hook := range ar.runnerHooks {
   366  		re, ok := hook.(interfaces.RunnerTaskRestartHook)
   367  		if !ok {
   368  			continue
   369  		}
   370  
   371  		name := re.Name()
   372  		var start time.Time
   373  		if ar.logger.IsTrace() {
   374  			start = time.Now()
   375  			ar.logger.Trace("running alloc task restart hook",
   376  				"name", name, "start", start)
   377  		}
   378  
   379  		re.PreTaskRestart()
   380  
   381  		if ar.logger.IsTrace() {
   382  			end := time.Now()
   383  			ar.logger.Trace("finished alloc task restart hook",
   384  				"name", name, "end", end, "duration", end.Sub(start))
   385  		}
   386  	}
   387  }