| package engine |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "os" |
| "sort" |
| "strings" |
| "sync" |
| "time" |
| |
| "github.com/docker/docker/pkg/common" |
| "github.com/docker/docker/pkg/ioutils" |
| ) |
| |
| // Installer is a standard interface for objects which can "install" themselves |
| // on an engine by registering handlers. |
| // This can be used as an entrypoint for external plugins etc. |
| type Installer interface { |
| Install(*Engine) error |
| } |
| |
| type Handler func(*Job) Status |
| |
| var globalHandlers map[string]Handler |
| |
| func init() { |
| globalHandlers = make(map[string]Handler) |
| } |
| |
| func Register(name string, handler Handler) error { |
| _, exists := globalHandlers[name] |
| if exists { |
| return fmt.Errorf("Can't overwrite global handler for command %s", name) |
| } |
| globalHandlers[name] = handler |
| return nil |
| } |
| |
| func unregister(name string) { |
| delete(globalHandlers, name) |
| } |
| |
| // The Engine is the core of Docker. |
| // It acts as a store for *containers*, and allows manipulation of these |
| // containers by executing *jobs*. |
| type Engine struct { |
| handlers map[string]Handler |
| catchall Handler |
| hack Hack // data for temporary hackery (see hack.go) |
| id string |
| Stdout io.Writer |
| Stderr io.Writer |
| Stdin io.Reader |
| Logging bool |
| tasks sync.WaitGroup |
| l sync.RWMutex // lock for shutdown |
| shutdownWait sync.WaitGroup |
| shutdown bool |
| onShutdown []func() // shutdown handlers |
| } |
| |
| func (eng *Engine) Register(name string, handler Handler) error { |
| _, exists := eng.handlers[name] |
| if exists { |
| return fmt.Errorf("Can't overwrite handler for command %s", name) |
| } |
| eng.handlers[name] = handler |
| return nil |
| } |
| |
| func (eng *Engine) RegisterCatchall(catchall Handler) { |
| eng.catchall = catchall |
| } |
| |
| // New initializes a new engine. |
| func New() *Engine { |
| eng := &Engine{ |
| handlers: make(map[string]Handler), |
| id: common.RandomString(), |
| Stdout: os.Stdout, |
| Stderr: os.Stderr, |
| Stdin: os.Stdin, |
| Logging: true, |
| } |
| eng.Register("commands", func(job *Job) Status { |
| for _, name := range eng.commands() { |
| job.Printf("%s\n", name) |
| } |
| return StatusOK |
| }) |
| // Copy existing global handlers |
| for k, v := range globalHandlers { |
| eng.handlers[k] = v |
| } |
| return eng |
| } |
| |
| func (eng *Engine) String() string { |
| return fmt.Sprintf("%s", eng.id[:8]) |
| } |
| |
| // Commands returns a list of all currently registered commands, |
| // sorted alphabetically. |
| func (eng *Engine) commands() []string { |
| names := make([]string, 0, len(eng.handlers)) |
| for name := range eng.handlers { |
| names = append(names, name) |
| } |
| sort.Strings(names) |
| return names |
| } |
| |
| // Job creates a new job which can later be executed. |
| // This function mimics `Command` from the standard os/exec package. |
| func (eng *Engine) Job(name string, args ...string) *Job { |
| job := &Job{ |
| Eng: eng, |
| Name: name, |
| Args: args, |
| Stdin: NewInput(), |
| Stdout: NewOutput(), |
| Stderr: NewOutput(), |
| env: &Env{}, |
| closeIO: true, |
| |
| cancelled: make(chan struct{}), |
| } |
| if eng.Logging { |
| job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr)) |
| } |
| |
| // Catchall is shadowed by specific Register. |
| if handler, exists := eng.handlers[name]; exists { |
| job.handler = handler |
| } else if eng.catchall != nil && name != "" { |
| // empty job names are illegal, catchall or not. |
| job.handler = eng.catchall |
| } |
| return job |
| } |
| |
| // OnShutdown registers a new callback to be called by Shutdown. |
| // This is typically used by services to perform cleanup. |
| func (eng *Engine) OnShutdown(h func()) { |
| eng.l.Lock() |
| eng.onShutdown = append(eng.onShutdown, h) |
| eng.shutdownWait.Add(1) |
| eng.l.Unlock() |
| } |
| |
| // Shutdown permanently shuts down eng as follows: |
| // - It refuses all new jobs, permanently. |
| // - It waits for all active jobs to complete (with no timeout) |
| // - It calls all shutdown handlers concurrently (if any) |
| // - It returns when all handlers complete, or after 15 seconds, |
| // whichever happens first. |
| func (eng *Engine) Shutdown() { |
| eng.l.Lock() |
| if eng.shutdown { |
| eng.l.Unlock() |
| eng.shutdownWait.Wait() |
| return |
| } |
| eng.shutdown = true |
| eng.l.Unlock() |
| // We don't need to protect the rest with a lock, to allow |
| // for other calls to immediately fail with "shutdown" instead |
| // of hanging for 15 seconds. |
| // This requires all concurrent calls to check for shutdown, otherwise |
| // it might cause a race. |
| |
| // Wait for all jobs to complete. |
| // Timeout after 5 seconds. |
| tasksDone := make(chan struct{}) |
| go func() { |
| eng.tasks.Wait() |
| close(tasksDone) |
| }() |
| select { |
| case <-time.After(time.Second * 5): |
| case <-tasksDone: |
| } |
| |
| // Call shutdown handlers, if any. |
| // Timeout after 10 seconds. |
| for _, h := range eng.onShutdown { |
| go func(h func()) { |
| h() |
| eng.shutdownWait.Done() |
| }(h) |
| } |
| done := make(chan struct{}) |
| go func() { |
| eng.shutdownWait.Wait() |
| close(done) |
| }() |
| select { |
| case <-time.After(time.Second * 10): |
| case <-done: |
| } |
| return |
| } |
| |
| // IsShutdown returns true if the engine is in the process |
| // of shutting down, or already shut down. |
| // Otherwise it returns false. |
| func (eng *Engine) IsShutdown() bool { |
| eng.l.RLock() |
| defer eng.l.RUnlock() |
| return eng.shutdown |
| } |
| |
| // ParseJob creates a new job from a text description using a shell-like syntax. |
| // |
| // The following syntax is used to parse `input`: |
| // |
| // * Words are separated using standard whitespaces as separators. |
| // * Quotes and backslashes are not interpreted. |
| // * Words of the form 'KEY=[VALUE]' are added to the job environment. |
| // * All other words are added to the job arguments. |
| // |
| // For example: |
| // |
| // job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world") |
| // |
| // The resulting job will have: |
| // job.Args={"echo", "hello", "world"} |
| // job.Env={"VERBOSE":"1", "TEST":"true"} |
| // |
| func (eng *Engine) ParseJob(input string) (*Job, error) { |
| // FIXME: use a full-featured command parser |
| scanner := bufio.NewScanner(strings.NewReader(input)) |
| scanner.Split(bufio.ScanWords) |
| var ( |
| cmd []string |
| env Env |
| ) |
| for scanner.Scan() { |
| word := scanner.Text() |
| kv := strings.SplitN(word, "=", 2) |
| if len(kv) == 2 { |
| env.Set(kv[0], kv[1]) |
| } else { |
| cmd = append(cmd, word) |
| } |
| } |
| if len(cmd) == 0 { |
| return nil, fmt.Errorf("empty command: '%s'", input) |
| } |
| job := eng.Job(cmd[0], cmd[1:]...) |
| job.Env().Init(&env) |
| return job, nil |
| } |