github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/driver.go (about)

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