open-nomad/client/allocrunner/task_hook_coordinator.go

202 lines
5.4 KiB
Go
Raw Normal View History

2019-12-04 20:44:21 +00:00
package allocrunner
import (
"context"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/allocrunner/taskrunner"
2019-12-04 20:44:21 +00:00
"github.com/hashicorp/nomad/nomad/structs"
)
2020-11-12 16:01:42 +00:00
// TaskHookCoordinator helps coordinate when mainTasks start tasks can launch
2019-12-04 20:44:21 +00:00
// namely after all Prestart Tasks have run, and after all BlockUntilCompleted have completed
type taskHookCoordinator struct {
logger hclog.Logger
2020-11-12 16:01:42 +00:00
// constant for quickly starting all prestart tasks
2019-12-04 20:44:21 +00:00
closedCh chan struct{}
2020-11-12 16:01:42 +00:00
// Each context is used to gate task runners launching the tasks. A task
// runner waits until the context associated its lifecycle context is
// done/cancelled.
2019-12-04 20:44:21 +00:00
mainTaskCtx context.Context
mainTaskCtxCancel func()
poststartTaskCtx context.Context
poststartTaskCtxCancel func()
2020-11-12 16:01:42 +00:00
poststopTaskCtx context.Context
poststopTaskCtxCancel context.CancelFunc
prestartSidecar map[string]struct{}
prestartEphemeral map[string]struct{}
2020-11-12 16:01:42 +00:00
mainTasksRunning map[string]struct{} // poststop: main tasks running -> finished
mainTasksPending map[string]struct{} // poststart: main tasks pending -> running
2019-12-04 20:44:21 +00:00
}
func newTaskHookCoordinator(logger hclog.Logger, tasks []*structs.Task) *taskHookCoordinator {
closedCh := make(chan struct{})
close(closedCh)
mainTaskCtx, mainCancelFn := context.WithCancel(context.Background())
poststartTaskCtx, poststartCancelFn := context.WithCancel(context.Background())
2020-11-12 16:01:42 +00:00
poststopTaskCtx, poststopTaskCancelFn := context.WithCancel(context.Background())
2019-12-04 20:44:21 +00:00
c := &taskHookCoordinator{
logger: logger,
closedCh: closedCh,
mainTaskCtx: mainTaskCtx,
mainTaskCtxCancel: mainCancelFn,
prestartSidecar: map[string]struct{}{},
prestartEphemeral: map[string]struct{}{},
2020-11-12 16:01:42 +00:00
mainTasksRunning: map[string]struct{}{},
mainTasksPending: map[string]struct{}{},
poststartTaskCtx: poststartTaskCtx,
poststartTaskCtxCancel: poststartCancelFn,
2020-11-12 16:01:42 +00:00
poststopTaskCtx: poststopTaskCtx,
poststopTaskCtxCancel: poststopTaskCancelFn,
2019-12-04 20:44:21 +00:00
}
c.setTasks(tasks)
return c
}
func (c *taskHookCoordinator) setTasks(tasks []*structs.Task) {
for _, task := range tasks {
2020-03-21 21:52:11 +00:00
if task.Lifecycle == nil {
c.mainTasksPending[task.Name] = struct{}{}
2020-11-12 16:01:42 +00:00
c.mainTasksRunning[task.Name] = struct{}{}
2019-12-04 20:44:21 +00:00
continue
}
2020-03-21 21:52:11 +00:00
switch task.Lifecycle.Hook {
case structs.TaskLifecycleHookPrestart:
if task.Lifecycle.Sidecar {
c.prestartSidecar[task.Name] = struct{}{}
} else {
c.prestartEphemeral[task.Name] = struct{}{}
}
case structs.TaskLifecycleHookPoststart:
// Poststart hooks don't need to be tracked.
2020-11-12 16:01:42 +00:00
case structs.TaskLifecycleHookPoststop:
// Poststop hooks don't need to be tracked.
2020-03-21 21:52:11 +00:00
default:
c.logger.Error("invalid lifecycle hook", "task", task.Name, "hook", task.Lifecycle.Hook)
2019-12-04 20:44:21 +00:00
}
}
2020-03-21 21:52:11 +00:00
if !c.hasPrestartTasks() {
2019-12-04 20:44:21 +00:00
c.mainTaskCtxCancel()
}
}
2020-03-21 21:52:11 +00:00
func (c *taskHookCoordinator) hasPrestartTasks() bool {
return len(c.prestartSidecar)+len(c.prestartEphemeral) > 0
}
2020-11-12 16:01:42 +00:00
func (c *taskHookCoordinator) hasRunningMainTasks() bool {
return len(c.mainTasksRunning) > 0
}
func (c *taskHookCoordinator) hasPendingMainTasks() bool {
return len(c.mainTasksPending) > 0
}
2019-12-04 20:44:21 +00:00
func (c *taskHookCoordinator) startConditionForTask(task *structs.Task) <-chan struct{} {
if task.Lifecycle == nil {
return c.mainTaskCtx.Done()
2019-12-04 20:44:21 +00:00
}
switch task.Lifecycle.Hook {
case structs.TaskLifecycleHookPrestart:
// Prestart tasks start without checking status of other tasks
return c.closedCh
case structs.TaskLifecycleHookPoststart:
return c.poststartTaskCtx.Done()
2020-11-12 16:01:42 +00:00
case structs.TaskLifecycleHookPoststop:
return c.poststopTaskCtx.Done()
default:
2020-11-12 16:01:42 +00:00
// it should never have a lifecycle stanza w/o a hook, so report an error but allow the task to start normally
c.logger.Error("invalid lifecycle hook", "task", task.Name, "hook", task.Lifecycle.Hook)
return c.mainTaskCtx.Done()
}
2019-12-04 20:44:21 +00:00
}
// This is not thread safe! This must only be called from one thread per alloc runner.
2019-12-04 20:44:21 +00:00
func (c *taskHookCoordinator) taskStateUpdated(states map[string]*structs.TaskState) {
for task := range c.prestartSidecar {
2019-12-04 20:44:21 +00:00
st := states[task]
if st == nil || st.StartedAt.IsZero() {
continue
}
delete(c.prestartSidecar, task)
2019-12-04 20:44:21 +00:00
}
for task := range c.prestartEphemeral {
2019-12-04 20:44:21 +00:00
st := states[task]
if st == nil || !st.Successful() {
continue
}
delete(c.prestartEphemeral, task)
2019-12-04 20:44:21 +00:00
}
2020-11-12 16:01:42 +00:00
for task := range c.mainTasksRunning {
st := states[task]
if st == nil || st.State != structs.TaskStateDead {
continue
}
delete(c.mainTasksRunning, task)
}
for task := range c.mainTasksPending {
st := states[task]
if st == nil || st.StartedAt.IsZero() {
continue
}
delete(c.mainTasksPending, task)
}
2020-03-21 21:52:11 +00:00
if !c.hasPrestartTasks() {
2019-12-04 20:44:21 +00:00
c.mainTaskCtxCancel()
}
if !c.hasPendingMainTasks() {
c.poststartTaskCtxCancel()
}
2020-11-12 16:01:42 +00:00
if !c.hasRunningMainTasks() {
c.poststopTaskCtxCancel()
}
}
func (c *taskHookCoordinator) StartPoststopTasks() {
c.poststopTaskCtxCancel()
2019-12-04 20:44:21 +00:00
}
// hasNonSidecarTasks returns false if all the passed tasks are sidecar tasks
func hasNonSidecarTasks(tasks []*taskrunner.TaskRunner) bool {
for _, tr := range tasks {
lc := tr.Task().Lifecycle
if lc == nil || !lc.Sidecar {
return true
}
}
return false
}
// hasSidecarTasks returns true if all the passed tasks are sidecar tasks
func hasSidecarTasks(tasks map[string]*taskrunner.TaskRunner) bool {
for _, tr := range tasks {
lc := tr.Task().Lifecycle
if lc != nil && lc.Sidecar {
return true
}
}
return false
}