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