github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/client/driver/raw_exec.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/client/allocdir"
    14  	"github.com/hashicorp/nomad/client/config"
    15  	"github.com/hashicorp/nomad/client/driver/args"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  )
    18  
    19  const (
    20  	// The option that enables this driver in the Config.Options map.
    21  	rawExecConfigOption = "driver.raw_exec.enable"
    22  
    23  	// Null files to use as stdin.
    24  	unixNull    = "/dev/null"
    25  	windowsNull = "nul"
    26  )
    27  
    28  // The RawExecDriver is a privileged version of the exec driver. It provides no
    29  // resource isolation and just fork/execs. The Exec driver should be preferred
    30  // and this should only be used when explicitly needed.
    31  type RawExecDriver struct {
    32  	DriverContext
    33  }
    34  
    35  // rawExecHandle is returned from Start/Open as a handle to the PID
    36  type rawExecHandle struct {
    37  	proc   *os.Process
    38  	waitCh chan error
    39  	doneCh chan struct{}
    40  }
    41  
    42  // NewRawExecDriver is used to create a new raw exec driver
    43  func NewRawExecDriver(ctx *DriverContext) Driver {
    44  	return &RawExecDriver{*ctx}
    45  }
    46  
    47  func (d *RawExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    48  	// Check that the user has explicitly enabled this executor.
    49  	enabled, err := strconv.ParseBool(cfg.ReadDefault(rawExecConfigOption, "false"))
    50  	if err != nil {
    51  		return false, fmt.Errorf("Failed to parse %v option: %v", rawExecConfigOption, err)
    52  	}
    53  
    54  	if enabled {
    55  		d.logger.Printf("[WARN] driver.raw_exec: raw exec is enabled. Only enable if needed")
    56  		node.Attributes["driver.raw_exec"] = "1"
    57  		return true, nil
    58  	}
    59  
    60  	return false, nil
    61  }
    62  
    63  func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
    64  	// Get the command
    65  	command, ok := task.Config["command"]
    66  	if !ok || command == "" {
    67  		return nil, fmt.Errorf("missing command for raw_exec driver")
    68  	}
    69  
    70  	// Get the tasks local directory.
    71  	taskName := d.DriverContext.taskName
    72  	taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
    73  	if !ok {
    74  		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
    75  	}
    76  	taskLocal := filepath.Join(taskDir, allocdir.TaskLocal)
    77  
    78  	// Get the environment variables.
    79  	envVars := TaskEnvironmentVariables(ctx, task)
    80  
    81  	// Look for arguments
    82  	var cmdArgs []string
    83  	if argRaw, ok := task.Config["args"]; ok {
    84  		parsed, err := args.ParseAndReplace(argRaw, envVars.Map())
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		cmdArgs = append(cmdArgs, parsed...)
    89  	}
    90  
    91  	// Setup the command
    92  	cmd := exec.Command(command, cmdArgs...)
    93  	cmd.Dir = taskDir
    94  	cmd.Env = envVars.List()
    95  
    96  	// Capture the stdout/stderr and redirect stdin to /dev/null
    97  	stdoutFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stdout", taskName))
    98  	stderrFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stderr", taskName))
    99  	stdinFilename := unixNull
   100  	if runtime.GOOS == "windows" {
   101  		stdinFilename = windowsNull
   102  	}
   103  
   104  	stdo, err := os.OpenFile(stdoutFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("Error opening file to redirect stdout: %v", err)
   107  	}
   108  
   109  	stde, err := os.OpenFile(stderrFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   110  	if err != nil {
   111  		return nil, fmt.Errorf("Error opening file to redirect stderr: %v", err)
   112  	}
   113  
   114  	stdi, err := os.OpenFile(stdinFilename, os.O_CREATE|os.O_RDONLY, 0666)
   115  	if err != nil {
   116  		return nil, fmt.Errorf("Error opening file to redirect stdin: %v", err)
   117  	}
   118  
   119  	cmd.Stdout = stdo
   120  	cmd.Stderr = stde
   121  	cmd.Stdin = stdi
   122  
   123  	if err := cmd.Start(); err != nil {
   124  		return nil, fmt.Errorf("failed to start command: %v", err)
   125  	}
   126  
   127  	// Return a driver handle
   128  	h := &rawExecHandle{
   129  		proc:   cmd.Process,
   130  		doneCh: make(chan struct{}),
   131  		waitCh: make(chan error, 1),
   132  	}
   133  	go h.run()
   134  	return h, nil
   135  }
   136  
   137  func (d *RawExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   138  	// Split the handle
   139  	pidStr := strings.TrimPrefix(handleID, "PID:")
   140  	pid, err := strconv.Atoi(pidStr)
   141  	if err != nil {
   142  		return nil, fmt.Errorf("failed to parse handle '%s': %v", handleID, err)
   143  	}
   144  
   145  	// Find the process
   146  	proc, err := os.FindProcess(pid)
   147  	if proc == nil || err != nil {
   148  		return nil, fmt.Errorf("failed to find PID %d: %v", pid, err)
   149  	}
   150  
   151  	// Return a driver handle
   152  	h := &rawExecHandle{
   153  		proc:   proc,
   154  		doneCh: make(chan struct{}),
   155  		waitCh: make(chan error, 1),
   156  	}
   157  	go h.run()
   158  	return h, nil
   159  }
   160  
   161  func (h *rawExecHandle) ID() string {
   162  	// Return a handle to the PID
   163  	return fmt.Sprintf("PID:%d", h.proc.Pid)
   164  }
   165  
   166  func (h *rawExecHandle) WaitCh() chan error {
   167  	return h.waitCh
   168  }
   169  
   170  func (h *rawExecHandle) Update(task *structs.Task) error {
   171  	// Update is not possible
   172  	return nil
   173  }
   174  
   175  // Kill is used to terminate the task. We send an Interrupt
   176  // and then provide a 5 second grace period before doing a Kill on supported
   177  // OS's, otherwise we kill immediately.
   178  func (h *rawExecHandle) Kill() error {
   179  	if runtime.GOOS == "windows" {
   180  		return h.proc.Kill()
   181  	}
   182  
   183  	h.proc.Signal(os.Interrupt)
   184  	select {
   185  	case <-h.doneCh:
   186  		return nil
   187  	case <-time.After(5 * time.Second):
   188  		return h.proc.Kill()
   189  	}
   190  }
   191  
   192  func (h *rawExecHandle) run() {
   193  	ps, err := h.proc.Wait()
   194  	close(h.doneCh)
   195  	if err != nil {
   196  		h.waitCh <- err
   197  	} else if !ps.Success() {
   198  		h.waitCh <- fmt.Errorf("task exited with error")
   199  	}
   200  	close(h.waitCh)
   201  }