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 }