github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/alloc_runner.go (about) 1 package client 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 "sync" 9 "time" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/nomad/client/allocdir" 13 "github.com/hashicorp/nomad/client/config" 14 "github.com/hashicorp/nomad/client/driver" 15 "github.com/hashicorp/nomad/client/vaultclient" 16 "github.com/hashicorp/nomad/nomad/structs" 17 18 cstructs "github.com/hashicorp/nomad/client/structs" 19 ) 20 21 const ( 22 // taskReceivedSyncLimit is how long the client will wait before sending 23 // that a task was received to the server. The client does not immediately 24 // send that the task was received to the server because another transition 25 // to running or failed is likely to occur immediately after and a single 26 // update will transfer all past state information. If not other transition 27 // has occurred up to this limit, we will send to the server. 28 taskReceivedSyncLimit = 30 * time.Second 29 ) 30 31 // AllocStateUpdater is used to update the status of an allocation 32 type AllocStateUpdater func(alloc *structs.Allocation) 33 34 type AllocStatsReporter interface { 35 LatestAllocStats(taskFilter string) (*cstructs.AllocResourceUsage, error) 36 } 37 38 // AllocRunner is used to wrap an allocation and provide the execution context. 39 type AllocRunner struct { 40 config *config.Config 41 updater AllocStateUpdater 42 logger *log.Logger 43 44 alloc *structs.Allocation 45 allocClientStatus string // Explicit status of allocation. Set when there are failures 46 allocClientDescription string 47 allocLock sync.Mutex 48 49 dirtyCh chan struct{} 50 51 ctx *driver.ExecContext 52 ctxLock sync.Mutex 53 tasks map[string]*TaskRunner 54 taskStates map[string]*structs.TaskState 55 restored map[string]struct{} 56 taskLock sync.RWMutex 57 58 taskStatusLock sync.RWMutex 59 60 updateCh chan *structs.Allocation 61 62 vaultClient vaultclient.VaultClient 63 64 otherAllocDir *allocdir.AllocDir 65 66 destroy bool 67 destroyCh chan struct{} 68 destroyLock sync.Mutex 69 waitCh chan struct{} 70 71 // serialize saveAllocRunnerState calls 72 persistLock sync.Mutex 73 } 74 75 // allocRunnerState is used to snapshot the state of the alloc runner 76 type allocRunnerState struct { 77 Version string 78 Alloc *structs.Allocation 79 AllocClientStatus string 80 AllocClientDescription string 81 Context *driver.ExecContext 82 } 83 84 // NewAllocRunner is used to create a new allocation context 85 func NewAllocRunner(logger *log.Logger, config *config.Config, updater AllocStateUpdater, 86 alloc *structs.Allocation, vaultClient vaultclient.VaultClient) *AllocRunner { 87 ar := &AllocRunner{ 88 config: config, 89 updater: updater, 90 logger: logger, 91 alloc: alloc, 92 dirtyCh: make(chan struct{}, 1), 93 tasks: make(map[string]*TaskRunner), 94 taskStates: copyTaskStates(alloc.TaskStates), 95 restored: make(map[string]struct{}), 96 updateCh: make(chan *structs.Allocation, 64), 97 destroyCh: make(chan struct{}), 98 waitCh: make(chan struct{}), 99 vaultClient: vaultClient, 100 } 101 return ar 102 } 103 104 // stateFilePath returns the path to our state file 105 func (r *AllocRunner) stateFilePath() string { 106 r.allocLock.Lock() 107 defer r.allocLock.Unlock() 108 path := filepath.Join(r.config.StateDir, "alloc", r.alloc.ID, "state.json") 109 return path 110 } 111 112 // RestoreState is used to restore the state of the alloc runner 113 func (r *AllocRunner) RestoreState() error { 114 // Load the snapshot 115 var snap allocRunnerState 116 if err := restoreState(r.stateFilePath(), &snap); err != nil { 117 return err 118 } 119 120 // Restore fields 121 r.alloc = snap.Alloc 122 r.ctx = snap.Context 123 r.allocClientStatus = snap.AllocClientStatus 124 r.allocClientDescription = snap.AllocClientDescription 125 126 var snapshotErrors multierror.Error 127 if r.alloc == nil { 128 snapshotErrors.Errors = append(snapshotErrors.Errors, fmt.Errorf("alloc_runner snapshot includes a nil allocation")) 129 } 130 if r.ctx == nil { 131 snapshotErrors.Errors = append(snapshotErrors.Errors, fmt.Errorf("alloc_runner snapshot includes a nil context")) 132 } 133 if e := snapshotErrors.ErrorOrNil(); e != nil { 134 return e 135 } 136 137 r.taskStates = snap.Alloc.TaskStates 138 139 // Restore the task runners 140 var mErr multierror.Error 141 for name, state := range r.taskStates { 142 // Mark the task as restored. 143 r.restored[name] = struct{}{} 144 145 task := &structs.Task{Name: name} 146 tr := NewTaskRunner(r.logger, r.config, r.setTaskState, r.ctx, r.Alloc(), 147 task, r.vaultClient) 148 r.tasks[name] = tr 149 150 // Skip tasks in terminal states. 151 if state.State == structs.TaskStateDead { 152 continue 153 } 154 155 if err := tr.RestoreState(); err != nil { 156 r.logger.Printf("[ERR] client: failed to restore state for alloc %s task '%s': %v", r.alloc.ID, name, err) 157 mErr.Errors = append(mErr.Errors, err) 158 } else if !r.alloc.TerminalStatus() { 159 // Only start if the alloc isn't in a terminal status. 160 go tr.Run() 161 } 162 } 163 164 return mErr.ErrorOrNil() 165 } 166 167 // GetAllocDir returns the alloc dir for the alloc runner 168 func (r *AllocRunner) GetAllocDir() *allocdir.AllocDir { 169 if r.ctx == nil { 170 return nil 171 } 172 return r.ctx.AllocDir 173 } 174 175 // SaveState is used to snapshot the state of the alloc runner 176 // if the fullSync is marked as false only the state of the Alloc Runner 177 // is snapshotted. If fullSync is marked as true, we snapshot 178 // all the Task Runners associated with the Alloc 179 func (r *AllocRunner) SaveState() error { 180 if err := r.saveAllocRunnerState(); err != nil { 181 return err 182 } 183 184 // Save state for each task 185 runners := r.getTaskRunners() 186 var mErr multierror.Error 187 for _, tr := range runners { 188 if err := r.saveTaskRunnerState(tr); err != nil { 189 mErr.Errors = append(mErr.Errors, err) 190 } 191 } 192 return mErr.ErrorOrNil() 193 } 194 195 func (r *AllocRunner) saveAllocRunnerState() error { 196 r.persistLock.Lock() 197 defer r.persistLock.Unlock() 198 199 // Create the snapshot. 200 alloc := r.Alloc() 201 202 r.allocLock.Lock() 203 allocClientStatus := r.allocClientStatus 204 allocClientDescription := r.allocClientDescription 205 r.allocLock.Unlock() 206 207 r.ctxLock.Lock() 208 ctx := r.ctx 209 r.ctxLock.Unlock() 210 211 snap := allocRunnerState{ 212 Version: r.config.Version, 213 Alloc: alloc, 214 Context: ctx, 215 AllocClientStatus: allocClientStatus, 216 AllocClientDescription: allocClientDescription, 217 } 218 return persistState(r.stateFilePath(), &snap) 219 } 220 221 func (r *AllocRunner) saveTaskRunnerState(tr *TaskRunner) error { 222 if err := tr.SaveState(); err != nil { 223 return fmt.Errorf("failed to save state for alloc %s task '%s': %v", 224 r.alloc.ID, tr.task.Name, err) 225 } 226 return nil 227 } 228 229 // DestroyState is used to cleanup after ourselves 230 func (r *AllocRunner) DestroyState() error { 231 return os.RemoveAll(filepath.Dir(r.stateFilePath())) 232 } 233 234 // DestroyContext is used to destroy the context 235 func (r *AllocRunner) DestroyContext() error { 236 return r.ctx.AllocDir.Destroy() 237 } 238 239 // copyTaskStates returns a copy of the passed task states. 240 func copyTaskStates(states map[string]*structs.TaskState) map[string]*structs.TaskState { 241 copy := make(map[string]*structs.TaskState, len(states)) 242 for task, state := range states { 243 copy[task] = state.Copy() 244 } 245 return copy 246 } 247 248 // Alloc returns the associated allocation 249 func (r *AllocRunner) Alloc() *structs.Allocation { 250 r.allocLock.Lock() 251 alloc := r.alloc.Copy() 252 253 // The status has explicitly been set. 254 if r.allocClientStatus != "" || r.allocClientDescription != "" { 255 alloc.ClientStatus = r.allocClientStatus 256 alloc.ClientDescription = r.allocClientDescription 257 258 // Copy over the task states so we don't lose them 259 r.taskStatusLock.RLock() 260 alloc.TaskStates = copyTaskStates(r.taskStates) 261 r.taskStatusLock.RUnlock() 262 263 r.allocLock.Unlock() 264 return alloc 265 } 266 r.allocLock.Unlock() 267 268 // Scan the task states to determine the status of the alloc 269 var pending, running, dead, failed bool 270 r.taskStatusLock.RLock() 271 alloc.TaskStates = copyTaskStates(r.taskStates) 272 for _, state := range r.taskStates { 273 switch state.State { 274 case structs.TaskStateRunning: 275 running = true 276 case structs.TaskStatePending: 277 pending = true 278 case structs.TaskStateDead: 279 if state.Failed { 280 failed = true 281 } else { 282 dead = true 283 } 284 } 285 } 286 r.taskStatusLock.RUnlock() 287 288 // Determine the alloc status 289 if failed { 290 alloc.ClientStatus = structs.AllocClientStatusFailed 291 } else if running { 292 alloc.ClientStatus = structs.AllocClientStatusRunning 293 } else if pending { 294 alloc.ClientStatus = structs.AllocClientStatusPending 295 } else if dead { 296 alloc.ClientStatus = structs.AllocClientStatusComplete 297 } 298 299 return alloc 300 } 301 302 // dirtySyncState is used to watch for state being marked dirty to sync 303 func (r *AllocRunner) dirtySyncState() { 304 for { 305 select { 306 case <-r.dirtyCh: 307 r.syncStatus() 308 case <-r.destroyCh: 309 return 310 } 311 } 312 } 313 314 // syncStatus is used to run and sync the status when it changes 315 func (r *AllocRunner) syncStatus() error { 316 // Get a copy of our alloc, update status server side and sync to disk 317 alloc := r.Alloc() 318 r.updater(alloc) 319 return r.saveAllocRunnerState() 320 } 321 322 // setStatus is used to update the allocation status 323 func (r *AllocRunner) setStatus(status, desc string) { 324 r.allocLock.Lock() 325 r.allocClientStatus = status 326 r.allocClientDescription = desc 327 r.allocLock.Unlock() 328 select { 329 case r.dirtyCh <- struct{}{}: 330 default: 331 } 332 } 333 334 // setTaskState is used to set the status of a task. If state is empty then the 335 // event is appended but not synced with the server. The event may be omitted 336 func (r *AllocRunner) setTaskState(taskName, state string, event *structs.TaskEvent) { 337 r.taskStatusLock.Lock() 338 defer r.taskStatusLock.Unlock() 339 taskState, ok := r.taskStates[taskName] 340 if !ok { 341 taskState = &structs.TaskState{} 342 r.taskStates[taskName] = taskState 343 } 344 345 // Set the tasks state. 346 if event != nil { 347 if event.FailsTask { 348 taskState.Failed = true 349 } 350 r.appendTaskEvent(taskState, event) 351 } 352 353 if state == "" { 354 return 355 } 356 357 taskState.State = state 358 if state == structs.TaskStateDead { 359 // If the task failed, we should kill all the other tasks in the task group. 360 if taskState.Failed { 361 var destroyingTasks []string 362 for task, tr := range r.tasks { 363 if task != taskName { 364 destroyingTasks = append(destroyingTasks, task) 365 tr.Destroy(structs.NewTaskEvent(structs.TaskSiblingFailed).SetFailedSibling(taskName)) 366 } 367 } 368 if len(destroyingTasks) > 0 { 369 r.logger.Printf("[DEBUG] client: task %q failed, destroying other tasks in task group: %v", taskName, destroyingTasks) 370 } 371 } 372 } 373 374 select { 375 case r.dirtyCh <- struct{}{}: 376 default: 377 } 378 } 379 380 // appendTaskEvent updates the task status by appending the new event. 381 func (r *AllocRunner) appendTaskEvent(state *structs.TaskState, event *structs.TaskEvent) { 382 capacity := 10 383 if state.Events == nil { 384 state.Events = make([]*structs.TaskEvent, 0, capacity) 385 } 386 387 // If we hit capacity, then shift it. 388 if len(state.Events) == capacity { 389 old := state.Events 390 state.Events = make([]*structs.TaskEvent, 0, capacity) 391 state.Events = append(state.Events, old[1:]...) 392 } 393 394 state.Events = append(state.Events, event) 395 } 396 397 // Run is a long running goroutine used to manage an allocation 398 func (r *AllocRunner) Run() { 399 defer close(r.waitCh) 400 go r.dirtySyncState() 401 402 // Find the task group to run in the allocation 403 alloc := r.alloc 404 tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup) 405 if tg == nil { 406 r.logger.Printf("[ERR] client: alloc '%s' for missing task group '%s'", alloc.ID, alloc.TaskGroup) 407 r.setStatus(structs.AllocClientStatusFailed, fmt.Sprintf("missing task group '%s'", alloc.TaskGroup)) 408 return 409 } 410 411 // Create the execution context 412 r.ctxLock.Lock() 413 if r.ctx == nil { 414 allocDir := allocdir.NewAllocDir(filepath.Join(r.config.AllocDir, r.alloc.ID)) 415 if err := allocDir.Build(tg.Tasks); err != nil { 416 r.logger.Printf("[WARN] client: failed to build task directories: %v", err) 417 r.setStatus(structs.AllocClientStatusFailed, fmt.Sprintf("failed to build task dirs for '%s'", alloc.TaskGroup)) 418 r.ctxLock.Unlock() 419 return 420 } 421 r.ctx = driver.NewExecContext(allocDir, r.alloc.ID) 422 if r.otherAllocDir != nil { 423 if err := allocDir.Move(r.otherAllocDir, tg.Tasks); err != nil { 424 r.logger.Printf("[ERROR] client: failed to move alloc dir into alloc %q: %v", r.alloc.ID, err) 425 } 426 if err := r.otherAllocDir.Destroy(); err != nil { 427 r.logger.Printf("[ERROR] client: error destroying allocdir %v", r.otherAllocDir.AllocDir, err) 428 } 429 } 430 } 431 r.ctxLock.Unlock() 432 433 // Check if the allocation is in a terminal status. In this case, we don't 434 // start any of the task runners and directly wait for the destroy signal to 435 // clean up the allocation. 436 if alloc.TerminalStatus() { 437 r.logger.Printf("[DEBUG] client: alloc %q in terminal status, waiting for destroy", r.alloc.ID) 438 r.handleDestroy() 439 r.logger.Printf("[DEBUG] client: terminating runner for alloc '%s'", r.alloc.ID) 440 return 441 } 442 443 // Start the task runners 444 r.logger.Printf("[DEBUG] client: starting task runners for alloc '%s'", r.alloc.ID) 445 r.taskLock.Lock() 446 for _, task := range tg.Tasks { 447 if _, ok := r.restored[task.Name]; ok { 448 continue 449 } 450 451 tr := NewTaskRunner(r.logger, r.config, r.setTaskState, r.ctx, r.Alloc(), task.Copy(), r.vaultClient) 452 r.tasks[task.Name] = tr 453 tr.MarkReceived() 454 455 go tr.Run() 456 } 457 r.taskLock.Unlock() 458 459 // taskDestroyEvent contains an event that caused the destroyment of a task 460 // in the allocation. 461 var taskDestroyEvent *structs.TaskEvent 462 463 OUTER: 464 // Wait for updates 465 for { 466 select { 467 case update := <-r.updateCh: 468 // Store the updated allocation. 469 r.allocLock.Lock() 470 r.alloc = update 471 r.allocLock.Unlock() 472 473 // Check if we're in a terminal status 474 if update.TerminalStatus() { 475 taskDestroyEvent = structs.NewTaskEvent(structs.TaskKilled) 476 break OUTER 477 } 478 479 // Update the task groups 480 runners := r.getTaskRunners() 481 for _, tr := range runners { 482 tr.Update(update) 483 } 484 case <-r.destroyCh: 485 taskDestroyEvent = structs.NewTaskEvent(structs.TaskKilled) 486 break OUTER 487 } 488 } 489 490 // Kill the task runners 491 r.destroyTaskRunners(taskDestroyEvent) 492 493 // Block until we should destroy the state of the alloc 494 r.handleDestroy() 495 r.logger.Printf("[DEBUG] client: terminating runner for alloc '%s'", r.alloc.ID) 496 } 497 498 // SetPreviousAllocDir sets the previous allocation directory of the current 499 // allocation 500 func (r *AllocRunner) SetPreviousAllocDir(allocDir *allocdir.AllocDir) { 501 r.otherAllocDir = allocDir 502 } 503 504 // destroyTaskRunners destroys the task runners, waits for them to terminate and 505 // then saves state. 506 func (r *AllocRunner) destroyTaskRunners(destroyEvent *structs.TaskEvent) { 507 // Destroy each sub-task 508 runners := r.getTaskRunners() 509 for _, tr := range runners { 510 tr.Destroy(destroyEvent) 511 } 512 513 // Wait for termination of the task runners 514 for _, tr := range runners { 515 <-tr.WaitCh() 516 } 517 518 // Final state sync 519 r.syncStatus() 520 } 521 522 // handleDestroy blocks till the AllocRunner should be destroyed and does the 523 // necessary cleanup. 524 func (r *AllocRunner) handleDestroy() { 525 select { 526 case <-r.destroyCh: 527 if err := r.DestroyContext(); err != nil { 528 r.logger.Printf("[ERR] client: failed to destroy context for alloc '%s': %v", 529 r.alloc.ID, err) 530 } 531 if err := r.DestroyState(); err != nil { 532 r.logger.Printf("[ERR] client: failed to destroy state for alloc '%s': %v", 533 r.alloc.ID, err) 534 } 535 } 536 } 537 538 // Update is used to update the allocation of the context 539 func (r *AllocRunner) Update(update *structs.Allocation) { 540 select { 541 case r.updateCh <- update: 542 default: 543 r.logger.Printf("[ERR] client: dropping update to alloc '%s'", update.ID) 544 } 545 } 546 547 // StatsReporter returns an interface to query resource usage statistics of an 548 // allocation 549 func (r *AllocRunner) StatsReporter() AllocStatsReporter { 550 return r 551 } 552 553 // getTaskRunners is a helper that returns a copy of the task runners list using 554 // the taskLock. 555 func (r *AllocRunner) getTaskRunners() []*TaskRunner { 556 // Get the task runners 557 r.taskLock.RLock() 558 defer r.taskLock.RUnlock() 559 runners := make([]*TaskRunner, 0, len(r.tasks)) 560 for _, tr := range r.tasks { 561 runners = append(runners, tr) 562 } 563 return runners 564 } 565 566 // LatestAllocStats returns the latest allocation stats. If the optional taskFilter is set 567 // the allocation stats will only include the given task. 568 func (r *AllocRunner) LatestAllocStats(taskFilter string) (*cstructs.AllocResourceUsage, error) { 569 astat := &cstructs.AllocResourceUsage{ 570 Tasks: make(map[string]*cstructs.TaskResourceUsage), 571 } 572 573 var flat []*cstructs.TaskResourceUsage 574 if taskFilter != "" { 575 r.taskLock.RLock() 576 tr, ok := r.tasks[taskFilter] 577 r.taskLock.RUnlock() 578 if !ok { 579 return nil, fmt.Errorf("allocation %q has no task %q", r.alloc.ID, taskFilter) 580 } 581 l := tr.LatestResourceUsage() 582 if l != nil { 583 astat.Tasks[taskFilter] = l 584 flat = []*cstructs.TaskResourceUsage{l} 585 astat.Timestamp = l.Timestamp 586 } 587 } else { 588 // Get the task runners 589 runners := r.getTaskRunners() 590 for _, tr := range runners { 591 l := tr.LatestResourceUsage() 592 if l != nil { 593 astat.Tasks[tr.task.Name] = l 594 flat = append(flat, l) 595 if l.Timestamp > astat.Timestamp { 596 astat.Timestamp = l.Timestamp 597 } 598 } 599 } 600 } 601 602 astat.ResourceUsage = sumTaskResourceUsage(flat) 603 return astat, nil 604 } 605 606 // sumTaskResourceUsage takes a set of task resources and sums their resources 607 func sumTaskResourceUsage(usages []*cstructs.TaskResourceUsage) *cstructs.ResourceUsage { 608 summed := &cstructs.ResourceUsage{ 609 MemoryStats: &cstructs.MemoryStats{}, 610 CpuStats: &cstructs.CpuStats{}, 611 } 612 for _, usage := range usages { 613 summed.Add(usage.ResourceUsage) 614 } 615 return summed 616 } 617 618 // shouldUpdate takes the AllocModifyIndex of an allocation sent from the server and 619 // checks if the current running allocation is behind and should be updated. 620 func (r *AllocRunner) shouldUpdate(serverIndex uint64) bool { 621 r.allocLock.Lock() 622 defer r.allocLock.Unlock() 623 return r.alloc.AllocModifyIndex < serverIndex 624 } 625 626 // Destroy is used to indicate that the allocation context should be destroyed 627 func (r *AllocRunner) Destroy() { 628 r.destroyLock.Lock() 629 defer r.destroyLock.Unlock() 630 631 if r.destroy { 632 return 633 } 634 r.destroy = true 635 close(r.destroyCh) 636 } 637 638 // WaitCh returns a channel to wait for termination 639 func (r *AllocRunner) WaitCh() <-chan struct{} { 640 return r.waitCh 641 }