github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/plugins/drivers/driver.go (about)

     1  package drivers
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"sort"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/client/allocdir"
    14  	cstructs "github.com/hashicorp/nomad/client/structs"
    15  	"github.com/hashicorp/nomad/helper"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/plugins/base"
    18  	"github.com/hashicorp/nomad/plugins/drivers/proto"
    19  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    20  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    21  	"github.com/zclconf/go-cty/cty"
    22  	"github.com/zclconf/go-cty/cty/msgpack"
    23  )
    24  
    25  const (
    26  	// DriverHealthy is the default health description that should be used
    27  	// if the driver is nominal
    28  	DriverHealthy = "Healthy"
    29  
    30  	// Pre09TaskHandleVersion is the version used to identify that the task
    31  	// handle is from a driver that existed before driver plugins (v0.9). The
    32  	// driver should take appropriate action to handle the old driver state.
    33  	Pre09TaskHandleVersion = 0
    34  )
    35  
    36  // DriverPlugin is the interface with drivers will implement. It is also
    37  // implemented by a plugin client which proxies the calls to go-plugin. See
    38  // the proto/driver.proto file for detailed information about each RPC and
    39  // message structure.
    40  type DriverPlugin interface {
    41  	base.BasePlugin
    42  
    43  	TaskConfigSchema() (*hclspec.Spec, error)
    44  	Capabilities() (*Capabilities, error)
    45  	Fingerprint(context.Context) (<-chan *Fingerprint, error)
    46  
    47  	RecoverTask(*TaskHandle) error
    48  	StartTask(*TaskConfig) (*TaskHandle, *DriverNetwork, error)
    49  	WaitTask(ctx context.Context, taskID string) (<-chan *ExitResult, error)
    50  	StopTask(taskID string, timeout time.Duration, signal string) error
    51  	DestroyTask(taskID string, force bool) error
    52  	InspectTask(taskID string) (*TaskStatus, error)
    53  	TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *cstructs.TaskResourceUsage, error)
    54  	TaskEvents(context.Context) (<-chan *TaskEvent, error)
    55  
    56  	SignalTask(taskID string, signal string) error
    57  	ExecTask(taskID string, cmd []string, timeout time.Duration) (*ExecTaskResult, error)
    58  }
    59  
    60  // ExecTaskStreamingDriver marks that a driver supports streaming exec task.  This represents a user friendly
    61  // interface to implement, as an alternative to the ExecTaskStreamingRawDriver, the low level interface.
    62  type ExecTaskStreamingDriver interface {
    63  	ExecTaskStreaming(ctx context.Context, taskID string, execOptions *ExecOptions) (*ExitResult, error)
    64  }
    65  
    66  type ExecOptions struct {
    67  	// Command is command to run
    68  	Command []string
    69  
    70  	// Tty indicates whether pseudo-terminal is to be allocated
    71  	Tty bool
    72  
    73  	// streams
    74  	Stdin  io.ReadCloser
    75  	Stdout io.WriteCloser
    76  	Stderr io.WriteCloser
    77  
    78  	// terminal size channel
    79  	ResizeCh <-chan TerminalSize
    80  }
    81  
    82  // InternalDriverPlugin is an interface that exposes functions that are only
    83  // implemented by internal driver plugins.
    84  type InternalDriverPlugin interface {
    85  	// Shutdown allows the plugin to cleanup any running state to avoid leaking
    86  	// resources. It should not block.
    87  	Shutdown()
    88  }
    89  
    90  // DriverSignalTaskNotSupported can be embedded by drivers which don't support
    91  // the SignalTask RPC. This satisfies the SignalTask func requirement for the
    92  // DriverPlugin interface.
    93  type DriverSignalTaskNotSupported struct{}
    94  
    95  func (DriverSignalTaskNotSupported) SignalTask(taskID, signal string) error {
    96  	return fmt.Errorf("SignalTask is not supported by this driver")
    97  }
    98  
    99  // DriverExecTaskNotSupported can be embedded by drivers which don't support
   100  // the ExecTask RPC. This satisfies the ExecTask func requirement of the
   101  // DriverPlugin interface.
   102  type DriverExecTaskNotSupported struct{}
   103  
   104  func (_ DriverExecTaskNotSupported) ExecTask(taskID, signal string) error {
   105  	return fmt.Errorf("ExecTask is not supported by this driver")
   106  }
   107  
   108  type HealthState string
   109  
   110  var (
   111  	HealthStateUndetected = HealthState("undetected")
   112  	HealthStateUnhealthy  = HealthState("unhealthy")
   113  	HealthStateHealthy    = HealthState("healthy")
   114  )
   115  
   116  type Fingerprint struct {
   117  	Attributes        map[string]*pstructs.Attribute
   118  	Health            HealthState
   119  	HealthDescription string
   120  
   121  	// Err is set by the plugin if an error occurred during fingerprinting
   122  	Err error
   123  }
   124  
   125  // FSIsolation is an enumeration to describe what kind of filesystem isolation
   126  // a driver supports.
   127  type FSIsolation string
   128  
   129  var (
   130  	// FSIsolationNone means no isolation. The host filesystem is used.
   131  	FSIsolationNone = FSIsolation("none")
   132  
   133  	// FSIsolationChroot means the driver will use a chroot on the host
   134  	// filesystem.
   135  	FSIsolationChroot = FSIsolation("chroot")
   136  
   137  	// FSIsolationImage means the driver uses an image.
   138  	FSIsolationImage = FSIsolation("image")
   139  )
   140  
   141  type Capabilities struct {
   142  	// SendSignals marks the driver as being able to send signals
   143  	SendSignals bool
   144  
   145  	// Exec marks the driver as being able to execute arbitrary commands
   146  	// such as health checks. Used by the ScriptExecutor interface.
   147  	Exec bool
   148  
   149  	//FSIsolation indicates what kind of filesystem isolation the driver supports.
   150  	FSIsolation FSIsolation
   151  }
   152  
   153  type TerminalSize struct {
   154  	Height int
   155  	Width  int
   156  }
   157  
   158  type TaskConfig struct {
   159  	ID              string
   160  	JobName         string
   161  	TaskGroupName   string
   162  	Name            string
   163  	Env             map[string]string
   164  	DeviceEnv       map[string]string
   165  	Resources       *Resources
   166  	Devices         []*DeviceConfig
   167  	Mounts          []*MountConfig
   168  	User            string
   169  	AllocDir        string
   170  	rawDriverConfig []byte
   171  	StdoutPath      string
   172  	StderrPath      string
   173  	AllocID         string
   174  }
   175  
   176  func (tc *TaskConfig) Copy() *TaskConfig {
   177  	if tc == nil {
   178  		return nil
   179  	}
   180  	c := new(TaskConfig)
   181  	*c = *tc
   182  	c.Env = helper.CopyMapStringString(c.Env)
   183  	c.DeviceEnv = helper.CopyMapStringString(c.DeviceEnv)
   184  	c.Resources = tc.Resources.Copy()
   185  
   186  	if c.Devices != nil {
   187  		dc := make([]*DeviceConfig, len(c.Devices))
   188  		for i, c := range c.Devices {
   189  			dc[i] = c.Copy()
   190  		}
   191  		c.Devices = dc
   192  	}
   193  
   194  	if c.Mounts != nil {
   195  		mc := make([]*MountConfig, len(c.Mounts))
   196  		for i, m := range c.Mounts {
   197  			mc[i] = m.Copy()
   198  		}
   199  		c.Mounts = mc
   200  	}
   201  
   202  	return c
   203  }
   204  
   205  func (tc *TaskConfig) EnvList() []string {
   206  	l := make([]string, 0, len(tc.Env))
   207  	for k, v := range tc.Env {
   208  		l = append(l, k+"="+v)
   209  	}
   210  
   211  	sort.Strings(l)
   212  	return l
   213  }
   214  
   215  func (tc *TaskConfig) TaskDir() *allocdir.TaskDir {
   216  	taskDir := filepath.Join(tc.AllocDir, tc.Name)
   217  	return &allocdir.TaskDir{
   218  		Dir:            taskDir,
   219  		SharedAllocDir: filepath.Join(tc.AllocDir, allocdir.SharedAllocName),
   220  		LogDir:         filepath.Join(tc.AllocDir, allocdir.SharedAllocName, allocdir.LogDirName),
   221  		SharedTaskDir:  filepath.Join(taskDir, allocdir.SharedAllocName),
   222  		LocalDir:       filepath.Join(taskDir, allocdir.TaskLocal),
   223  		SecretsDir:     filepath.Join(taskDir, allocdir.TaskSecrets),
   224  	}
   225  }
   226  
   227  func (tc *TaskConfig) DecodeDriverConfig(t interface{}) error {
   228  	return base.MsgPackDecode(tc.rawDriverConfig, t)
   229  }
   230  
   231  func (tc *TaskConfig) EncodeDriverConfig(val cty.Value) error {
   232  	data, err := msgpack.Marshal(val, val.Type())
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	tc.rawDriverConfig = data
   238  	return nil
   239  }
   240  
   241  func (tc *TaskConfig) EncodeConcreteDriverConfig(t interface{}) error {
   242  	data := []byte{}
   243  	err := base.MsgPackEncode(&data, t)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	tc.rawDriverConfig = data
   249  	return nil
   250  }
   251  
   252  type Resources struct {
   253  	NomadResources *structs.AllocatedTaskResources
   254  	LinuxResources *LinuxResources
   255  }
   256  
   257  func (r *Resources) Copy() *Resources {
   258  	if r == nil {
   259  		return nil
   260  	}
   261  	res := new(Resources)
   262  	if r.NomadResources != nil {
   263  		res.NomadResources = r.NomadResources.Copy()
   264  	}
   265  	if r.LinuxResources != nil {
   266  		res.LinuxResources = r.LinuxResources.Copy()
   267  	}
   268  	return res
   269  }
   270  
   271  type LinuxResources struct {
   272  	CPUPeriod        int64
   273  	CPUQuota         int64
   274  	CPUShares        int64
   275  	MemoryLimitBytes int64
   276  	OOMScoreAdj      int64
   277  	CpusetCPUs       string
   278  	CpusetMems       string
   279  
   280  	// PrecentTicks is used to calculate the CPUQuota, currently the docker
   281  	// driver exposes cpu period and quota through the driver configuration
   282  	// and thus the calculation for CPUQuota cannot be done on the client.
   283  	// This is a capatability and should only be used by docker until the docker
   284  	// specific options are deprecated in favor of exposes CPUPeriod and
   285  	// CPUQuota at the task resource stanza.
   286  	PercentTicks float64
   287  }
   288  
   289  func (r *LinuxResources) Copy() *LinuxResources {
   290  	res := new(LinuxResources)
   291  	*res = *r
   292  	return res
   293  }
   294  
   295  type DeviceConfig struct {
   296  	TaskPath    string
   297  	HostPath    string
   298  	Permissions string
   299  }
   300  
   301  func (d *DeviceConfig) Copy() *DeviceConfig {
   302  	if d == nil {
   303  		return nil
   304  	}
   305  
   306  	dc := new(DeviceConfig)
   307  	*dc = *d
   308  	return dc
   309  }
   310  
   311  type MountConfig struct {
   312  	TaskPath string
   313  	HostPath string
   314  	Readonly bool
   315  }
   316  
   317  func (m *MountConfig) Copy() *MountConfig {
   318  	if m == nil {
   319  		return nil
   320  	}
   321  
   322  	mc := new(MountConfig)
   323  	*mc = *m
   324  	return mc
   325  }
   326  
   327  const (
   328  	TaskStateUnknown TaskState = "unknown"
   329  	TaskStateRunning TaskState = "running"
   330  	TaskStateExited  TaskState = "exited"
   331  )
   332  
   333  type TaskState string
   334  
   335  type ExitResult struct {
   336  	ExitCode  int
   337  	Signal    int
   338  	OOMKilled bool
   339  	Err       error
   340  }
   341  
   342  func (r *ExitResult) Successful() bool {
   343  	return r.ExitCode == 0 && r.Signal == 0 && r.Err == nil
   344  }
   345  
   346  func (r *ExitResult) Copy() *ExitResult {
   347  	if r == nil {
   348  		return nil
   349  	}
   350  	res := new(ExitResult)
   351  	*res = *r
   352  	return res
   353  }
   354  
   355  type TaskStatus struct {
   356  	ID               string
   357  	Name             string
   358  	State            TaskState
   359  	StartedAt        time.Time
   360  	CompletedAt      time.Time
   361  	ExitResult       *ExitResult
   362  	DriverAttributes map[string]string
   363  	NetworkOverride  *DriverNetwork
   364  }
   365  
   366  type TaskEvent struct {
   367  	TaskID      string
   368  	TaskName    string
   369  	AllocID     string
   370  	Timestamp   time.Time
   371  	Message     string
   372  	Annotations map[string]string
   373  
   374  	// Err is only used if an error occurred while consuming the RPC stream
   375  	Err error
   376  }
   377  
   378  type ExecTaskResult struct {
   379  	Stdout     []byte
   380  	Stderr     []byte
   381  	ExitResult *ExitResult
   382  }
   383  
   384  // DriverNetwork is the network created by driver's (eg Docker's bridge
   385  // network) during Prestart.
   386  type DriverNetwork struct {
   387  	// PortMap can be set by drivers to replace ports in environment
   388  	// variables with driver-specific mappings.
   389  	PortMap map[string]int
   390  
   391  	// IP is the IP address for the task created by the driver.
   392  	IP string
   393  
   394  	// AutoAdvertise indicates whether the driver thinks services that
   395  	// choose to auto-advertise-addresses should use this IP instead of the
   396  	// host's. eg If a Docker network plugin is used
   397  	AutoAdvertise bool
   398  }
   399  
   400  // Advertise returns true if the driver suggests using the IP set. May be
   401  // called on a nil Network in which case it returns false.
   402  func (d *DriverNetwork) Advertise() bool {
   403  	return d != nil && d.AutoAdvertise
   404  }
   405  
   406  // Copy a DriverNetwork struct. If it is nil, nil is returned.
   407  func (d *DriverNetwork) Copy() *DriverNetwork {
   408  	if d == nil {
   409  		return nil
   410  	}
   411  	pm := make(map[string]int, len(d.PortMap))
   412  	for k, v := range d.PortMap {
   413  		pm[k] = v
   414  	}
   415  	return &DriverNetwork{
   416  		PortMap:       pm,
   417  		IP:            d.IP,
   418  		AutoAdvertise: d.AutoAdvertise,
   419  	}
   420  }
   421  
   422  // Hash the contents of a DriverNetwork struct to detect changes. If it is nil,
   423  // an empty slice is returned.
   424  func (d *DriverNetwork) Hash() []byte {
   425  	if d == nil {
   426  		return []byte{}
   427  	}
   428  	h := md5.New()
   429  	io.WriteString(h, d.IP)
   430  	io.WriteString(h, strconv.FormatBool(d.AutoAdvertise))
   431  	for k, v := range d.PortMap {
   432  		io.WriteString(h, k)
   433  		io.WriteString(h, strconv.Itoa(v))
   434  	}
   435  	return h.Sum(nil)
   436  }
   437  
   438  //// helper types for operating on raw exec operation
   439  // we alias proto instances as much as possible to avoid conversion overhead
   440  
   441  // ExecTaskStreamingRawDriver represents a low-level interface for executing a streaming exec
   442  // call, and is intended to be used when driver instance is to delegate exec handling to another
   443  // backend, e.g. to a executor or a driver behind a grpc/rpc protocol
   444  //
   445  // Nomad client would prefer this interface method over `ExecTaskStreaming` if driver implements it.
   446  type ExecTaskStreamingRawDriver interface {
   447  	ExecTaskStreamingRaw(
   448  		ctx context.Context,
   449  		taskID string,
   450  		command []string,
   451  		tty bool,
   452  		stream ExecTaskStream) error
   453  }
   454  
   455  // ExecTaskStream represents a stream of exec streaming messages,
   456  // and is a handle to get stdin and tty size and send back
   457  // stdout/stderr and exit operations.
   458  //
   459  // The methods are not concurrent safe; callers must ensure that methods are called
   460  // from at most one goroutine.
   461  type ExecTaskStream interface {
   462  	// Send relays response message back to API.
   463  	//
   464  	// The call is synchronous and no references to message is held: once
   465  	// method call completes, the message reference can be reused or freed.
   466  	Send(*ExecTaskStreamingResponseMsg) error
   467  
   468  	// Receive exec streaming messages from API.  Returns `io.EOF` on completion of stream.
   469  	Recv() (*ExecTaskStreamingRequestMsg, error)
   470  }
   471  
   472  type ExecTaskStreamingRequestMsg = proto.ExecTaskStreamingRequest
   473  type ExecTaskStreamingResponseMsg = proto.ExecTaskStreamingResponse