github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/client/driver/rkt.go (about)

     1  package driver
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"strings"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/hashicorp/go-version"
    18  	"github.com/hashicorp/nomad/client/allocdir"
    19  	"github.com/hashicorp/nomad/client/config"
    20  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    21  	"github.com/hashicorp/nomad/client/fingerprint"
    22  	"github.com/hashicorp/nomad/nomad/structs"
    23  	"github.com/mitchellh/mapstructure"
    24  )
    25  
    26  var (
    27  	reRktVersion  = regexp.MustCompile(`rkt version (\d[.\d]+)`)
    28  	reAppcVersion = regexp.MustCompile(`appc version (\d[.\d]+)`)
    29  )
    30  
    31  const (
    32  	// minRktVersion is the earliest supported version of rkt. rkt added support
    33  	// for CPU and memory isolators in 0.14.0. We cannot support an earlier
    34  	// version to maintain an uniform interface across all drivers
    35  	minRktVersion = "0.14.0"
    36  
    37  	// bytesToMB is the conversion from bytes to megabytes.
    38  	bytesToMB = 1024 * 1024
    39  )
    40  
    41  // RktDriver is a driver for running images via Rkt
    42  // We attempt to chose sane defaults for now, with more configuration available
    43  // planned in the future
    44  type RktDriver struct {
    45  	DriverContext
    46  	fingerprint.StaticFingerprinter
    47  }
    48  
    49  type RktDriverConfig struct {
    50  	ImageName string   `mapstructure:"image"`
    51  	Args      []string `mapstructure:"args"`
    52  }
    53  
    54  // rktHandle is returned from Start/Open as a handle to the PID
    55  type rktHandle struct {
    56  	proc        *os.Process
    57  	image       string
    58  	logger      *log.Logger
    59  	killTimeout time.Duration
    60  	waitCh      chan *cstructs.WaitResult
    61  	doneCh      chan struct{}
    62  }
    63  
    64  // rktPID is a struct to map the pid running the process to the vm image on
    65  // disk
    66  type rktPID struct {
    67  	Pid         int
    68  	Image       string
    69  	KillTimeout time.Duration
    70  }
    71  
    72  // NewRktDriver is used to create a new exec driver
    73  func NewRktDriver(ctx *DriverContext) Driver {
    74  	return &RktDriver{DriverContext: *ctx}
    75  }
    76  
    77  func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    78  	// Only enable if we are root when running on non-windows systems.
    79  	if runtime.GOOS != "windows" && syscall.Geteuid() != 0 {
    80  		d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling")
    81  		return false, nil
    82  	}
    83  
    84  	outBytes, err := exec.Command("rkt", "version").Output()
    85  	if err != nil {
    86  		return false, nil
    87  	}
    88  	out := strings.TrimSpace(string(outBytes))
    89  
    90  	rktMatches := reRktVersion.FindStringSubmatch(out)
    91  	appcMatches := reAppcVersion.FindStringSubmatch(out)
    92  	if len(rktMatches) != 2 || len(appcMatches) != 2 {
    93  		return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches)
    94  	}
    95  
    96  	node.Attributes["driver.rkt"] = "1"
    97  	node.Attributes["driver.rkt.version"] = rktMatches[1]
    98  	node.Attributes["driver.rkt.appc.version"] = appcMatches[1]
    99  
   100  	minVersion, _ := version.NewVersion(minRktVersion)
   101  	currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"])
   102  	if currentVersion.LessThan(minVersion) {
   103  		// Do not allow rkt < 0.14.0
   104  		d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion)
   105  		node.Attributes["driver.rkt"] = "0"
   106  	}
   107  	return true, nil
   108  }
   109  
   110  // Run an existing Rkt image.
   111  func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
   112  	var driverConfig RktDriverConfig
   113  	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
   114  		return nil, err
   115  	}
   116  	// Validate that the config is valid.
   117  	img := driverConfig.ImageName
   118  	if img == "" {
   119  		return nil, fmt.Errorf("Missing ACI image for rkt")
   120  	}
   121  
   122  	// Get the tasks local directory.
   123  	taskName := d.DriverContext.taskName
   124  	taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
   125  	if !ok {
   126  		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
   127  	}
   128  	taskLocal := filepath.Join(taskDir, allocdir.TaskLocal)
   129  
   130  	// Build the command.
   131  	var cmdArgs []string
   132  
   133  	// Add the given trust prefix
   134  	trustPrefix, trustCmd := task.Config["trust_prefix"]
   135  	if trustCmd {
   136  		var outBuf, errBuf bytes.Buffer
   137  		cmd := exec.Command("rkt", "trust", fmt.Sprintf("--prefix=%s", trustPrefix))
   138  		cmd.Stdout = &outBuf
   139  		cmd.Stderr = &errBuf
   140  		if err := cmd.Run(); err != nil {
   141  			return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s",
   142  				err, outBuf.String(), errBuf.String())
   143  		}
   144  		d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix)
   145  	} else {
   146  		// Disble signature verification if the trust command was not run.
   147  		cmdArgs = append(cmdArgs, "--insecure-options=all")
   148  	}
   149  
   150  	d.taskEnv.SetAllocDir(filepath.Join("/", allocdir.SharedAllocName)).
   151  		SetTaskLocalDir(filepath.Join("/", allocdir.TaskLocal)).Build()
   152  
   153  	for k, v := range d.taskEnv.EnvMap() {
   154  		cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%v", k, v))
   155  	}
   156  
   157  	// Append the run command.
   158  	cmdArgs = append(cmdArgs, "run", "--mds-register=false", img)
   159  
   160  	// Mount allc and task dirs
   161  	local, ok := ctx.AllocDir.TaskDirs[task.Name]
   162  	if !ok {
   163  		return nil, fmt.Errorf("Failed to find task local directory: %v", task.Name)
   164  	}
   165  	cmdArgs = append(cmdArgs, fmt.Sprintf("--volume %s,kind=empty,readOnly=false,source=%s --mount volume=data,target=%s", task.Name, local, ctx.AllocDir.SharedDir))
   166  
   167  	// Check if the user has overriden the exec command.
   168  	if execCmd, ok := task.Config["command"]; ok {
   169  		cmdArgs = append(cmdArgs, fmt.Sprintf("--exec=%v", execCmd))
   170  	}
   171  
   172  	if task.Resources.MemoryMB == 0 {
   173  		return nil, fmt.Errorf("Memory limit cannot be zero")
   174  	}
   175  	if task.Resources.CPU == 0 {
   176  		return nil, fmt.Errorf("CPU limit cannot be zero")
   177  	}
   178  
   179  	// Add memory isolator
   180  	cmdArgs = append(cmdArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB)*bytesToMB))
   181  
   182  	// Add CPU isolator
   183  	cmdArgs = append(cmdArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU)))
   184  
   185  	// Add user passed arguments.
   186  	if len(driverConfig.Args) != 0 {
   187  		parsed := d.taskEnv.ParseAndReplace(driverConfig.Args)
   188  
   189  		// Need to start arguments with "--"
   190  		if len(parsed) > 0 {
   191  			cmdArgs = append(cmdArgs, "--")
   192  		}
   193  
   194  		for _, arg := range parsed {
   195  			cmdArgs = append(cmdArgs, fmt.Sprintf("%v", arg))
   196  		}
   197  	}
   198  
   199  	// Create files to capture stdin and out.
   200  	stdoutFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stdout", taskName))
   201  	stderrFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stderr", taskName))
   202  
   203  	stdo, err := os.OpenFile(stdoutFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   204  	if err != nil {
   205  		return nil, fmt.Errorf("Error opening file to redirect stdout: %v", err)
   206  	}
   207  
   208  	stde, err := os.OpenFile(stderrFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   209  	if err != nil {
   210  		return nil, fmt.Errorf("Error opening file to redirect stderr: %v", err)
   211  	}
   212  
   213  	cmd := exec.Command("rkt", cmdArgs...)
   214  	cmd.Stdout = stdo
   215  	cmd.Stderr = stde
   216  
   217  	if err := cmd.Start(); err != nil {
   218  		return nil, fmt.Errorf("Error running rkt: %v", err)
   219  	}
   220  
   221  	d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmd.Args)
   222  	h := &rktHandle{
   223  		proc:        cmd.Process,
   224  		image:       img,
   225  		logger:      d.logger,
   226  		killTimeout: d.DriverContext.KillTimeout(task),
   227  		doneCh:      make(chan struct{}),
   228  		waitCh:      make(chan *cstructs.WaitResult, 1),
   229  	}
   230  	go h.run()
   231  	return h, nil
   232  }
   233  
   234  func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   235  	// Parse the handle
   236  	pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:"))
   237  	qpid := &rktPID{}
   238  	if err := json.Unmarshal(pidBytes, qpid); err != nil {
   239  		return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err)
   240  	}
   241  
   242  	// Find the process
   243  	proc, err := os.FindProcess(qpid.Pid)
   244  	if proc == nil || err != nil {
   245  		return nil, fmt.Errorf("failed to find Rkt PID %d: %v", qpid.Pid, err)
   246  	}
   247  
   248  	// Return a driver handle
   249  	h := &rktHandle{
   250  		proc:        proc,
   251  		image:       qpid.Image,
   252  		logger:      d.logger,
   253  		killTimeout: qpid.KillTimeout,
   254  		doneCh:      make(chan struct{}),
   255  		waitCh:      make(chan *cstructs.WaitResult, 1),
   256  	}
   257  
   258  	go h.run()
   259  	return h, nil
   260  }
   261  
   262  func (h *rktHandle) ID() string {
   263  	// Return a handle to the PID
   264  	pid := &rktPID{
   265  		Pid:         h.proc.Pid,
   266  		Image:       h.image,
   267  		KillTimeout: h.killTimeout,
   268  	}
   269  	data, err := json.Marshal(pid)
   270  	if err != nil {
   271  		h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err)
   272  	}
   273  	return fmt.Sprintf("Rkt:%s", string(data))
   274  }
   275  
   276  func (h *rktHandle) WaitCh() chan *cstructs.WaitResult {
   277  	return h.waitCh
   278  }
   279  
   280  func (h *rktHandle) Update(task *structs.Task) error {
   281  	// Update is not possible
   282  	return nil
   283  }
   284  
   285  // Kill is used to terminate the task. We send an Interrupt
   286  // and then provide a 5 second grace period before doing a Kill.
   287  func (h *rktHandle) Kill() error {
   288  	h.proc.Signal(os.Interrupt)
   289  	select {
   290  	case <-h.doneCh:
   291  		return nil
   292  	case <-time.After(h.killTimeout):
   293  		return h.proc.Kill()
   294  	}
   295  }
   296  
   297  func (h *rktHandle) run() {
   298  	ps, err := h.proc.Wait()
   299  	close(h.doneCh)
   300  	code := 0
   301  	if !ps.Success() {
   302  		// TODO: Better exit code parsing.
   303  		code = 1
   304  	}
   305  	h.waitCh <- cstructs.NewWaitResult(code, 0, err)
   306  	close(h.waitCh)
   307  }