github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/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/nomad/client/allocdir"
    18  	"github.com/hashicorp/nomad/client/config"
    19  	"github.com/hashicorp/nomad/client/driver/args"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  )
    22  
    23  var (
    24  	reRktVersion  = regexp.MustCompile(`rkt version (\d[.\d]+)`)
    25  	reAppcVersion = regexp.MustCompile(`appc version (\d[.\d]+)`)
    26  )
    27  
    28  // RktDriver is a driver for running images via Rkt
    29  // We attempt to chose sane defaults for now, with more configuration available
    30  // planned in the future
    31  type RktDriver struct {
    32  	DriverContext
    33  }
    34  
    35  // rktHandle is returned from Start/Open as a handle to the PID
    36  type rktHandle struct {
    37  	proc   *os.Process
    38  	image  string
    39  	logger *log.Logger
    40  	waitCh chan error
    41  	doneCh chan struct{}
    42  }
    43  
    44  // rktPID is a struct to map the pid running the process to the vm image on
    45  // disk
    46  type rktPID struct {
    47  	Pid   int
    48  	Image string
    49  }
    50  
    51  // NewRktDriver is used to create a new exec driver
    52  func NewRktDriver(ctx *DriverContext) Driver {
    53  	return &RktDriver{*ctx}
    54  }
    55  
    56  func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    57  	// Only enable if we are root when running on non-windows systems.
    58  	if runtime.GOOS != "windows" && syscall.Geteuid() != 0 {
    59  		d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling")
    60  		return false, nil
    61  	}
    62  
    63  	outBytes, err := exec.Command("rkt", "version").Output()
    64  	if err != nil {
    65  		return false, nil
    66  	}
    67  	out := strings.TrimSpace(string(outBytes))
    68  
    69  	rktMatches := reRktVersion.FindStringSubmatch(out)
    70  	appcMatches := reAppcVersion.FindStringSubmatch(out)
    71  	if len(rktMatches) != 2 || len(appcMatches) != 2 {
    72  		return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches)
    73  	}
    74  
    75  	node.Attributes["driver.rkt"] = "1"
    76  	node.Attributes["driver.rkt.version"] = rktMatches[1]
    77  	node.Attributes["driver.rkt.appc.version"] = appcMatches[1]
    78  
    79  	return true, nil
    80  }
    81  
    82  // Run an existing Rkt image.
    83  func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
    84  	// Validate that the config is valid.
    85  	img, ok := task.Config["image"]
    86  	if !ok || img == "" {
    87  		return nil, fmt.Errorf("Missing ACI image for rkt")
    88  	}
    89  
    90  	// Get the tasks local directory.
    91  	taskName := d.DriverContext.taskName
    92  	taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
    93  	if !ok {
    94  		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
    95  	}
    96  	taskLocal := filepath.Join(taskDir, allocdir.TaskLocal)
    97  
    98  	// Add the given trust prefix
    99  	trust_prefix, trust_cmd := task.Config["trust_prefix"]
   100  	if trust_cmd {
   101  		var outBuf, errBuf bytes.Buffer
   102  		cmd := exec.Command("rkt", "trust", fmt.Sprintf("--prefix=%s", trust_prefix))
   103  		cmd.Stdout = &outBuf
   104  		cmd.Stderr = &errBuf
   105  		if err := cmd.Run(); err != nil {
   106  			return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s",
   107  				err, outBuf.String(), errBuf.String())
   108  		}
   109  		d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trust_prefix)
   110  	}
   111  
   112  	// Build the command.
   113  	var cmd_args []string
   114  
   115  	// Inject the environment variables.
   116  	envVars := TaskEnvironmentVariables(ctx, task)
   117  
   118  	// Clear the task directories as they are not currently supported.
   119  	envVars.ClearTaskLocalDir()
   120  	envVars.ClearAllocDir()
   121  
   122  	for k, v := range envVars.Map() {
   123  		cmd_args = append(cmd_args, fmt.Sprintf("--set-env=%v=%v", k, v))
   124  	}
   125  
   126  	// Disble signature verification if the trust command was not run.
   127  	if !trust_cmd {
   128  		cmd_args = append(cmd_args, "--insecure-skip-verify")
   129  	}
   130  
   131  	// Append the run command.
   132  	cmd_args = append(cmd_args, "run", "--mds-register=false", img)
   133  
   134  	// Check if the user has overriden the exec command.
   135  	if exec_cmd, ok := task.Config["command"]; ok {
   136  		cmd_args = append(cmd_args, fmt.Sprintf("--exec=%v", exec_cmd))
   137  	}
   138  
   139  	// Add user passed arguments.
   140  	if userArgs, ok := task.Config["args"]; ok {
   141  		parsed, err := args.ParseAndReplace(userArgs, envVars.Map())
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  
   146  		// Need to start arguments with "--"
   147  		if len(parsed) > 0 {
   148  			cmd_args = append(cmd_args, "--")
   149  		}
   150  
   151  		for _, arg := range parsed {
   152  			cmd_args = append(cmd_args, fmt.Sprintf("%v", arg))
   153  		}
   154  	}
   155  
   156  	// Create files to capture stdin and out.
   157  	stdoutFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stdout", taskName))
   158  	stderrFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stderr", taskName))
   159  
   160  	stdo, err := os.OpenFile(stdoutFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   161  	if err != nil {
   162  		return nil, fmt.Errorf("Error opening file to redirect stdout: %v", err)
   163  	}
   164  
   165  	stde, err := os.OpenFile(stderrFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("Error opening file to redirect stderr: %v", err)
   168  	}
   169  
   170  	cmd := exec.Command("rkt", cmd_args...)
   171  	cmd.Stdout = stdo
   172  	cmd.Stderr = stde
   173  
   174  	if err := cmd.Start(); err != nil {
   175  		return nil, fmt.Errorf("Error running rkt: %v", err)
   176  	}
   177  
   178  	d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmd.Args)
   179  	h := &rktHandle{
   180  		proc:   cmd.Process,
   181  		image:  img,
   182  		logger: d.logger,
   183  		doneCh: make(chan struct{}),
   184  		waitCh: make(chan error, 1),
   185  	}
   186  	go h.run()
   187  	return h, nil
   188  }
   189  
   190  func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   191  	// Parse the handle
   192  	pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:"))
   193  	qpid := &rktPID{}
   194  	if err := json.Unmarshal(pidBytes, qpid); err != nil {
   195  		return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err)
   196  	}
   197  
   198  	// Find the process
   199  	proc, err := os.FindProcess(qpid.Pid)
   200  	if proc == nil || err != nil {
   201  		return nil, fmt.Errorf("failed to find Rkt PID %d: %v", qpid.Pid, err)
   202  	}
   203  
   204  	// Return a driver handle
   205  	h := &rktHandle{
   206  		proc:   proc,
   207  		image:  qpid.Image,
   208  		logger: d.logger,
   209  		doneCh: make(chan struct{}),
   210  		waitCh: make(chan error, 1),
   211  	}
   212  
   213  	go h.run()
   214  	return h, nil
   215  }
   216  
   217  func (h *rktHandle) ID() string {
   218  	// Return a handle to the PID
   219  	pid := &rktPID{
   220  		Pid:   h.proc.Pid,
   221  		Image: h.image,
   222  	}
   223  	data, err := json.Marshal(pid)
   224  	if err != nil {
   225  		h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err)
   226  	}
   227  	return fmt.Sprintf("Rkt:%s", string(data))
   228  }
   229  
   230  func (h *rktHandle) WaitCh() chan error {
   231  	return h.waitCh
   232  }
   233  
   234  func (h *rktHandle) Update(task *structs.Task) error {
   235  	// Update is not possible
   236  	return nil
   237  }
   238  
   239  // Kill is used to terminate the task. We send an Interrupt
   240  // and then provide a 5 second grace period before doing a Kill.
   241  func (h *rktHandle) Kill() error {
   242  	h.proc.Signal(os.Interrupt)
   243  	select {
   244  	case <-h.doneCh:
   245  		return nil
   246  	case <-time.After(5 * time.Second):
   247  		return h.proc.Kill()
   248  	}
   249  }
   250  
   251  func (h *rktHandle) run() {
   252  	ps, err := h.proc.Wait()
   253  	close(h.doneCh)
   254  	if err != nil {
   255  		h.waitCh <- err
   256  	} else if !ps.Success() {
   257  		h.waitCh <- fmt.Errorf("task exited with error")
   258  	}
   259  	close(h.waitCh)
   260  }