github.com/erriapo/docker@v1.6.0-rc2/engine/engine.go (about)

     1  package engine
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/docker/docker/pkg/common"
    14  	"github.com/docker/docker/pkg/ioutils"
    15  )
    16  
    17  // Installer is a standard interface for objects which can "install" themselves
    18  // on an engine by registering handlers.
    19  // This can be used as an entrypoint for external plugins etc.
    20  type Installer interface {
    21  	Install(*Engine) error
    22  }
    23  
    24  type Handler func(*Job) Status
    25  
    26  var globalHandlers map[string]Handler
    27  
    28  func init() {
    29  	globalHandlers = make(map[string]Handler)
    30  }
    31  
    32  func Register(name string, handler Handler) error {
    33  	_, exists := globalHandlers[name]
    34  	if exists {
    35  		return fmt.Errorf("Can't overwrite global handler for command %s", name)
    36  	}
    37  	globalHandlers[name] = handler
    38  	return nil
    39  }
    40  
    41  func unregister(name string) {
    42  	delete(globalHandlers, name)
    43  }
    44  
    45  // The Engine is the core of Docker.
    46  // It acts as a store for *containers*, and allows manipulation of these
    47  // containers by executing *jobs*.
    48  type Engine struct {
    49  	handlers     map[string]Handler
    50  	catchall     Handler
    51  	hack         Hack // data for temporary hackery (see hack.go)
    52  	id           string
    53  	Stdout       io.Writer
    54  	Stderr       io.Writer
    55  	Stdin        io.Reader
    56  	Logging      bool
    57  	tasks        sync.WaitGroup
    58  	l            sync.RWMutex // lock for shutdown
    59  	shutdownWait sync.WaitGroup
    60  	shutdown     bool
    61  	onShutdown   []func() // shutdown handlers
    62  }
    63  
    64  func (eng *Engine) Register(name string, handler Handler) error {
    65  	_, exists := eng.handlers[name]
    66  	if exists {
    67  		return fmt.Errorf("Can't overwrite handler for command %s", name)
    68  	}
    69  	eng.handlers[name] = handler
    70  	return nil
    71  }
    72  
    73  func (eng *Engine) RegisterCatchall(catchall Handler) {
    74  	eng.catchall = catchall
    75  }
    76  
    77  // New initializes a new engine.
    78  func New() *Engine {
    79  	eng := &Engine{
    80  		handlers: make(map[string]Handler),
    81  		id:       common.RandomString(),
    82  		Stdout:   os.Stdout,
    83  		Stderr:   os.Stderr,
    84  		Stdin:    os.Stdin,
    85  		Logging:  true,
    86  	}
    87  	eng.Register("commands", func(job *Job) Status {
    88  		for _, name := range eng.commands() {
    89  			job.Printf("%s\n", name)
    90  		}
    91  		return StatusOK
    92  	})
    93  	// Copy existing global handlers
    94  	for k, v := range globalHandlers {
    95  		eng.handlers[k] = v
    96  	}
    97  	return eng
    98  }
    99  
   100  func (eng *Engine) String() string {
   101  	return fmt.Sprintf("%s", eng.id[:8])
   102  }
   103  
   104  // Commands returns a list of all currently registered commands,
   105  // sorted alphabetically.
   106  func (eng *Engine) commands() []string {
   107  	names := make([]string, 0, len(eng.handlers))
   108  	for name := range eng.handlers {
   109  		names = append(names, name)
   110  	}
   111  	sort.Strings(names)
   112  	return names
   113  }
   114  
   115  // Job creates a new job which can later be executed.
   116  // This function mimics `Command` from the standard os/exec package.
   117  func (eng *Engine) Job(name string, args ...string) *Job {
   118  	job := &Job{
   119  		Eng:     eng,
   120  		Name:    name,
   121  		Args:    args,
   122  		Stdin:   NewInput(),
   123  		Stdout:  NewOutput(),
   124  		Stderr:  NewOutput(),
   125  		env:     &Env{},
   126  		closeIO: true,
   127  
   128  		cancelled: make(chan struct{}),
   129  	}
   130  	if eng.Logging {
   131  		job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr))
   132  	}
   133  
   134  	// Catchall is shadowed by specific Register.
   135  	if handler, exists := eng.handlers[name]; exists {
   136  		job.handler = handler
   137  	} else if eng.catchall != nil && name != "" {
   138  		// empty job names are illegal, catchall or not.
   139  		job.handler = eng.catchall
   140  	}
   141  	return job
   142  }
   143  
   144  // OnShutdown registers a new callback to be called by Shutdown.
   145  // This is typically used by services to perform cleanup.
   146  func (eng *Engine) OnShutdown(h func()) {
   147  	eng.l.Lock()
   148  	eng.onShutdown = append(eng.onShutdown, h)
   149  	eng.shutdownWait.Add(1)
   150  	eng.l.Unlock()
   151  }
   152  
   153  // Shutdown permanently shuts down eng as follows:
   154  // - It refuses all new jobs, permanently.
   155  // - It waits for all active jobs to complete (with no timeout)
   156  // - It calls all shutdown handlers concurrently (if any)
   157  // - It returns when all handlers complete, or after 15 seconds,
   158  //	whichever happens first.
   159  func (eng *Engine) Shutdown() {
   160  	eng.l.Lock()
   161  	if eng.shutdown {
   162  		eng.l.Unlock()
   163  		eng.shutdownWait.Wait()
   164  		return
   165  	}
   166  	eng.shutdown = true
   167  	eng.l.Unlock()
   168  	// We don't need to protect the rest with a lock, to allow
   169  	// for other calls to immediately fail with "shutdown" instead
   170  	// of hanging for 15 seconds.
   171  	// This requires all concurrent calls to check for shutdown, otherwise
   172  	// it might cause a race.
   173  
   174  	// Wait for all jobs to complete.
   175  	// Timeout after 5 seconds.
   176  	tasksDone := make(chan struct{})
   177  	go func() {
   178  		eng.tasks.Wait()
   179  		close(tasksDone)
   180  	}()
   181  	select {
   182  	case <-time.After(time.Second * 5):
   183  	case <-tasksDone:
   184  	}
   185  
   186  	// Call shutdown handlers, if any.
   187  	// Timeout after 10 seconds.
   188  	for _, h := range eng.onShutdown {
   189  		go func(h func()) {
   190  			h()
   191  			eng.shutdownWait.Done()
   192  		}(h)
   193  	}
   194  	done := make(chan struct{})
   195  	go func() {
   196  		eng.shutdownWait.Wait()
   197  		close(done)
   198  	}()
   199  	select {
   200  	case <-time.After(time.Second * 10):
   201  	case <-done:
   202  	}
   203  	return
   204  }
   205  
   206  // IsShutdown returns true if the engine is in the process
   207  // of shutting down, or already shut down.
   208  // Otherwise it returns false.
   209  func (eng *Engine) IsShutdown() bool {
   210  	eng.l.RLock()
   211  	defer eng.l.RUnlock()
   212  	return eng.shutdown
   213  }
   214  
   215  // ParseJob creates a new job from a text description using a shell-like syntax.
   216  //
   217  // The following syntax is used to parse `input`:
   218  //
   219  // * Words are separated using standard whitespaces as separators.
   220  // * Quotes and backslashes are not interpreted.
   221  // * Words of the form 'KEY=[VALUE]' are added to the job environment.
   222  // * All other words are added to the job arguments.
   223  //
   224  // For example:
   225  //
   226  // job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world")
   227  //
   228  // The resulting job will have:
   229  //	job.Args={"echo", "hello", "world"}
   230  //	job.Env={"VERBOSE":"1", "TEST":"true"}
   231  //
   232  func (eng *Engine) ParseJob(input string) (*Job, error) {
   233  	// FIXME: use a full-featured command parser
   234  	scanner := bufio.NewScanner(strings.NewReader(input))
   235  	scanner.Split(bufio.ScanWords)
   236  	var (
   237  		cmd []string
   238  		env Env
   239  	)
   240  	for scanner.Scan() {
   241  		word := scanner.Text()
   242  		kv := strings.SplitN(word, "=", 2)
   243  		if len(kv) == 2 {
   244  			env.Set(kv[0], kv[1])
   245  		} else {
   246  			cmd = append(cmd, word)
   247  		}
   248  	}
   249  	if len(cmd) == 0 {
   250  		return nil, fmt.Errorf("empty command: '%s'", input)
   251  	}
   252  	job := eng.Job(cmd[0], cmd[1:]...)
   253  	job.Env().Init(&env)
   254  	return job, nil
   255  }