github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/driver.go (about)

     1  package driver
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  
    12  	"github.com/hashicorp/nomad/client/allocdir"
    13  	"github.com/hashicorp/nomad/client/config"
    14  	"github.com/hashicorp/nomad/client/driver/env"
    15  	"github.com/hashicorp/nomad/client/fingerprint"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  
    18  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    19  	cstructs "github.com/hashicorp/nomad/client/structs"
    20  )
    21  
    22  var (
    23  	// BuiltinDrivers contains the built in registered drivers
    24  	// which are available for allocation handling
    25  	BuiltinDrivers = map[string]Factory{
    26  		"docker":   NewDockerDriver,
    27  		"exec":     NewExecDriver,
    28  		"raw_exec": NewRawExecDriver,
    29  		"java":     NewJavaDriver,
    30  		"qemu":     NewQemuDriver,
    31  		"rkt":      NewRktDriver,
    32  	}
    33  
    34  	// DriverStatsNotImplemented is the error to be returned if a driver doesn't
    35  	// implement stats.
    36  	DriverStatsNotImplemented = errors.New("stats not implemented for driver")
    37  )
    38  
    39  // NewDriver is used to instantiate and return a new driver
    40  // given the name and a logger
    41  func NewDriver(name string, ctx *DriverContext) (Driver, error) {
    42  	// Lookup the factory function
    43  	factory, ok := BuiltinDrivers[name]
    44  	if !ok {
    45  		return nil, fmt.Errorf("unknown driver '%s'", name)
    46  	}
    47  
    48  	// Instantiate the driver
    49  	d := factory(ctx)
    50  	return d, nil
    51  }
    52  
    53  // Factory is used to instantiate a new Driver
    54  type Factory func(*DriverContext) Driver
    55  
    56  // PrestartResponse is driver state returned by Driver.Prestart.
    57  type PrestartResponse struct {
    58  	// CreatedResources by the driver.
    59  	CreatedResources *CreatedResources
    60  
    61  	// Network contains driver-specific network parameters such as the port
    62  	// map between the host and a container.
    63  	//
    64  	// Since the network configuration may not be fully populated by
    65  	// Prestart, it will only be used for creating an environment for
    66  	// Start. It will be overridden by the DriverNetwork returned by Start.
    67  	Network *cstructs.DriverNetwork
    68  }
    69  
    70  // NewPrestartResponse creates a new PrestartResponse with CreatedResources
    71  // initialized.
    72  func NewPrestartResponse() *PrestartResponse {
    73  	return &PrestartResponse{
    74  		CreatedResources: NewCreatedResources(),
    75  	}
    76  }
    77  
    78  // CreatedResources is a map of resources (eg downloaded images) created by a driver
    79  // that must be cleaned up.
    80  type CreatedResources struct {
    81  	Resources map[string][]string
    82  }
    83  
    84  func NewCreatedResources() *CreatedResources {
    85  	return &CreatedResources{Resources: make(map[string][]string)}
    86  }
    87  
    88  // Add a new resource if it doesn't already exist.
    89  func (r *CreatedResources) Add(k, v string) {
    90  	if r.Resources == nil {
    91  		r.Resources = map[string][]string{k: []string{v}}
    92  		return
    93  	}
    94  	existing, ok := r.Resources[k]
    95  	if !ok {
    96  		// Key doesn't exist, create it
    97  		r.Resources[k] = []string{v}
    98  		return
    99  	}
   100  	for _, item := range existing {
   101  		if item == v {
   102  			// resource exists, return
   103  			return
   104  		}
   105  	}
   106  
   107  	// Resource type exists but value did not, append it
   108  	r.Resources[k] = append(existing, v)
   109  	return
   110  }
   111  
   112  // Remove a resource. Return true if removed, otherwise false.
   113  //
   114  // Removes the entire key if the needle is the last value in the list.
   115  func (r *CreatedResources) Remove(k, needle string) bool {
   116  	haystack := r.Resources[k]
   117  	for i, item := range haystack {
   118  		if item == needle {
   119  			r.Resources[k] = append(haystack[:i], haystack[i+1:]...)
   120  			if len(r.Resources[k]) == 0 {
   121  				delete(r.Resources, k)
   122  			}
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  // Copy returns a new deep copy of CreatedResrouces.
   130  func (r *CreatedResources) Copy() *CreatedResources {
   131  	if r == nil {
   132  		return nil
   133  	}
   134  
   135  	newr := CreatedResources{
   136  		Resources: make(map[string][]string, len(r.Resources)),
   137  	}
   138  	for k, v := range r.Resources {
   139  		newv := make([]string, len(v))
   140  		copy(newv, v)
   141  		newr.Resources[k] = newv
   142  	}
   143  	return &newr
   144  }
   145  
   146  // Merge another CreatedResources into this one. If the other CreatedResources
   147  // is nil this method is a noop.
   148  func (r *CreatedResources) Merge(o *CreatedResources) {
   149  	if o == nil {
   150  		return
   151  	}
   152  
   153  	for k, v := range o.Resources {
   154  		// New key
   155  		if len(r.Resources[k]) == 0 {
   156  			r.Resources[k] = v
   157  			continue
   158  		}
   159  
   160  		// Existing key
   161  	OUTER:
   162  		for _, item := range v {
   163  			for _, existing := range r.Resources[k] {
   164  				if item == existing {
   165  					// Found it, move on
   166  					continue OUTER
   167  				}
   168  			}
   169  
   170  			// New item, append it
   171  			r.Resources[k] = append(r.Resources[k], item)
   172  		}
   173  	}
   174  }
   175  
   176  func (r *CreatedResources) Hash() []byte {
   177  	h := md5.New()
   178  
   179  	for k, values := range r.Resources {
   180  		io.WriteString(h, k)
   181  		io.WriteString(h, "values")
   182  		for i, v := range values {
   183  			io.WriteString(h, fmt.Sprintf("%d-%v", i, v))
   184  		}
   185  	}
   186  
   187  	return h.Sum(nil)
   188  }
   189  
   190  // StartResponse is returned by Driver.Start.
   191  type StartResponse struct {
   192  	// Handle to the driver's task executor for controlling the lifecycle
   193  	// of the task.
   194  	Handle DriverHandle
   195  
   196  	// Network contains driver-specific network parameters such as the port
   197  	// map between the host and a container.
   198  	//
   199  	// Network may be nil as not all drivers or configurations create
   200  	// networks.
   201  	Network *cstructs.DriverNetwork
   202  }
   203  
   204  // Driver is used for execution of tasks. This allows Nomad
   205  // to support many pluggable implementations of task drivers.
   206  // Examples could include LXC, Docker, Qemu, etc.
   207  type Driver interface {
   208  	// Drivers must support the fingerprint interface for detection
   209  	fingerprint.Fingerprint
   210  
   211  	// Prestart prepares the task environment and performs expensive
   212  	// intialization steps like downloading images.
   213  	//
   214  	// CreatedResources may be non-nil even when an error occurs.
   215  	Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error)
   216  
   217  	// Start is used to begin task execution. If error is nil,
   218  	// StartResponse.Handle will be the handle to the task's executor.
   219  	// StartResponse.Network may be nil if the task doesn't configure a
   220  	// network.
   221  	Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error)
   222  
   223  	// Open is used to re-open a handle to a task
   224  	Open(ctx *ExecContext, handleID string) (DriverHandle, error)
   225  
   226  	// Cleanup is called to remove resources which were created for a task
   227  	// and no longer needed. Cleanup is not called if CreatedResources is
   228  	// nil.
   229  	//
   230  	// If Cleanup returns a recoverable error it may be retried. On retry
   231  	// it will be passed the same CreatedResources, so all successfully
   232  	// cleaned up resources should be removed or handled idempotently.
   233  	Cleanup(*ExecContext, *CreatedResources) error
   234  
   235  	// Drivers must validate their configuration
   236  	Validate(map[string]interface{}) error
   237  
   238  	// Abilities returns the abilities of the driver
   239  	Abilities() DriverAbilities
   240  
   241  	// FSIsolation returns the method of filesystem isolation used
   242  	FSIsolation() cstructs.FSIsolation
   243  }
   244  
   245  // DriverAbilities marks the abilities the driver has.
   246  type DriverAbilities struct {
   247  	// SendSignals marks the driver as being able to send signals
   248  	SendSignals bool
   249  
   250  	// Exec marks the driver as being able to execute arbitrary commands
   251  	// such as health checks. Used by the ScriptExecutor interface.
   252  	Exec bool
   253  }
   254  
   255  // LogEventFn is a callback which allows Drivers to emit task events.
   256  type LogEventFn func(message string, args ...interface{})
   257  
   258  // DriverContext is a means to inject dependencies such as loggers, configs, and
   259  // node attributes into a Driver without having to change the Driver interface
   260  // each time we do it. Used in conjection with Factory, above.
   261  type DriverContext struct {
   262  	taskName string
   263  	allocID  string
   264  	config   *config.Config
   265  	logger   *log.Logger
   266  	node     *structs.Node
   267  
   268  	emitEvent LogEventFn
   269  }
   270  
   271  // NewEmptyDriverContext returns a DriverContext with all fields set to their
   272  // zero value.
   273  func NewEmptyDriverContext() *DriverContext {
   274  	return &DriverContext{}
   275  }
   276  
   277  // NewDriverContext initializes a new DriverContext with the specified fields.
   278  // This enables other packages to create DriverContexts but keeps the fields
   279  // private to the driver. If we want to change this later we can gorename all of
   280  // the fields in DriverContext.
   281  func NewDriverContext(taskName, allocID string, config *config.Config, node *structs.Node,
   282  	logger *log.Logger, eventEmitter LogEventFn) *DriverContext {
   283  	return &DriverContext{
   284  		taskName:  taskName,
   285  		allocID:   allocID,
   286  		config:    config,
   287  		node:      node,
   288  		logger:    logger,
   289  		emitEvent: eventEmitter,
   290  	}
   291  }
   292  
   293  // DriverHandle is an opaque handle into a driver used for task
   294  // manipulation
   295  type DriverHandle interface {
   296  	// Returns an opaque handle that can be used to re-open the handle
   297  	ID() string
   298  
   299  	// WaitCh is used to return a channel used wait for task completion
   300  	WaitCh() chan *dstructs.WaitResult
   301  
   302  	// Update is used to update the task if possible and update task related
   303  	// configurations.
   304  	Update(task *structs.Task) error
   305  
   306  	// Kill is used to stop the task
   307  	Kill() error
   308  
   309  	// Stats returns aggregated stats of the driver
   310  	Stats() (*cstructs.TaskResourceUsage, error)
   311  
   312  	// Signal is used to send a signal to the task
   313  	Signal(s os.Signal) error
   314  
   315  	// ScriptExecutor is an interface used to execute commands such as
   316  	// health check scripts in the a DriverHandle's context.
   317  	ScriptExecutor
   318  }
   319  
   320  // ScriptExecutor is an interface that supports Exec()ing commands in the
   321  // driver's context. Split out of DriverHandle to ease testing.
   322  type ScriptExecutor interface {
   323  	Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error)
   324  }
   325  
   326  // ExecContext is a task's execution context
   327  type ExecContext struct {
   328  	// TaskDir contains information about the task directory structure.
   329  	TaskDir *allocdir.TaskDir
   330  
   331  	// TaskEnv contains the task's environment variables.
   332  	TaskEnv *env.TaskEnv
   333  }
   334  
   335  // NewExecContext is used to create a new execution context
   336  func NewExecContext(td *allocdir.TaskDir, te *env.TaskEnv) *ExecContext {
   337  	return &ExecContext{
   338  		TaskDir: td,
   339  		TaskEnv: te,
   340  	}
   341  }
   342  
   343  func mapMergeStrInt(maps ...map[string]int) map[string]int {
   344  	out := map[string]int{}
   345  	for _, in := range maps {
   346  		for key, val := range in {
   347  			out[key] = val
   348  		}
   349  	}
   350  	return out
   351  }
   352  
   353  func mapMergeStrStr(maps ...map[string]string) map[string]string {
   354  	out := map[string]string{}
   355  	for _, in := range maps {
   356  		for key, val := range in {
   357  			out[key] = val
   358  		}
   359  	}
   360  	return out
   361  }