Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A raw fork/exec driver that provides no isolation. #237

Merged
merged 5 commits into from
Oct 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions client/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import (
// BuiltinDrivers contains the built in registered drivers
// which are available for allocation handling
var BuiltinDrivers = map[string]Factory{
"docker": NewDockerDriver,
"exec": NewExecDriver,
"java": NewJavaDriver,
"qemu": NewQemuDriver,
"rkt": NewRktDriver,
"docker": NewDockerDriver,
"exec": NewExecDriver,
"raw_exec": NewRawExecDriver,
"java": NewJavaDriver,
"qemu": NewQemuDriver,
"rkt": NewRktDriver,
}

// NewDriver is used to instantiate and return a new driver
Expand Down Expand Up @@ -112,7 +113,7 @@ func TaskEnvironmentVariables(ctx *ExecContext, task *structs.Task) environment.
env.SetMeta(task.Meta)

if ctx.AllocDir != nil {
env.SetAllocDir(ctx.AllocDir.AllocDir)
env.SetAllocDir(ctx.AllocDir.SharedDir)
}

if task.Resources != nil {
Expand Down
3 changes: 1 addition & 2 deletions client/driver/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"testing"
"time"

"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/environment"
"github.com/hashicorp/nomad/nomad/structs"
Expand Down Expand Up @@ -159,7 +158,7 @@ func TestExecDriver_Start_Wait_AllocDir(t *testing.T) {
}

// Check that data was written to the shared alloc directory.
outputFile := filepath.Join(ctx.AllocDir.AllocDir, allocdir.SharedAllocName, file)
outputFile := filepath.Join(ctx.AllocDir.SharedDir, file)
act, err := ioutil.ReadFile(outputFile)
if err != nil {
t.Fatalf("Couldn't read expected output: %v", err)
Expand Down
201 changes: 201 additions & 0 deletions client/driver/raw_exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package driver

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"

"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/args"
"github.com/hashicorp/nomad/nomad/structs"
)

const (
// The option that enables this driver in the Config.Options map.
rawExecConfigOption = "driver.raw_exec.enable"

// Null files to use as stdin.
unixNull = "/dev/null"
windowsNull = "nul"
)

// The RawExecDriver is a privileged version of the exec driver. It provides no
// resource isolation and just fork/execs. The Exec driver should be preferred
// and this should only be used when explicitly needed.
type RawExecDriver struct {
DriverContext
}

// rawExecHandle is returned from Start/Open as a handle to the PID
type rawExecHandle struct {
proc *os.Process
waitCh chan error
doneCh chan struct{}
}

// NewRawExecDriver is used to create a new raw exec driver
func NewRawExecDriver(ctx *DriverContext) Driver {
return &RawExecDriver{*ctx}
}

func (d *RawExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
// Check that the user has explicitly enabled this executor.
enabled, err := strconv.ParseBool(cfg.ReadDefault(rawExecConfigOption, "false"))
if err != nil {
return false, fmt.Errorf("Failed to parse %v option: %v", rawExecConfigOption, err)
}

if enabled {
d.logger.Printf("[WARN] driver.raw_exec: raw exec is enabled. Only enable if needed")
node.Attributes["driver.raw_exec"] = "1"
return true, nil
}

return false, nil
}

func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
// Get the command
command, ok := task.Config["command"]
if !ok || command == "" {
return nil, fmt.Errorf("missing command for raw_exec driver")
}

// Get the tasks local directory.
taskName := d.DriverContext.taskName
taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
if !ok {
return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
}
taskLocal := filepath.Join(taskDir, allocdir.TaskLocal)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we verify that this directory exists before writing files to it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tasks are only added in that map after they have been made. So we are safe here


// Get the environment variables.
envVars := TaskEnvironmentVariables(ctx, task)

// Look for arguments
var cmdArgs []string
if argRaw, ok := task.Config["args"]; ok {
parsed, err := args.ParseAndReplace(argRaw, envVars.Map())
if err != nil {
return nil, err
}
cmdArgs = append(cmdArgs, parsed...)
}

// Setup the command
cmd := exec.Command(command, cmdArgs...)
cmd.Dir = taskDir
cmd.Env = envVars.List()

// Capture the stdout/stderr and redirect stdin to /dev/null
stdoutFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stdout", taskName))
stderrFilename := filepath.Join(taskLocal, fmt.Sprintf("%s.stderr", taskName))
stdinFilename := unixNull
if runtime.GOOS == "windows" {
stdinFilename = windowsNull
}

stdo, err := os.OpenFile(stdoutFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
if err != nil {
return nil, fmt.Errorf("Error opening file to redirect stdout: %v", err)
}

stde, err := os.OpenFile(stderrFilename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
if err != nil {
return nil, fmt.Errorf("Error opening file to redirect stderr: %v", err)
}

stdi, err := os.OpenFile(stdinFilename, os.O_CREATE|os.O_RDONLY, 0666)
if err != nil {
return nil, fmt.Errorf("Error opening file to redirect stdin: %v", err)
}

cmd.Stdout = stdo
cmd.Stderr = stde
cmd.Stdin = stdi

if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start command: %v", err)
}

// Return a driver handle
h := &rawExecHandle{
proc: cmd.Process,
doneCh: make(chan struct{}),
waitCh: make(chan error, 1),
}
go h.run()
return h, nil
}

func (d *RawExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
// Split the handle
pidStr := strings.TrimPrefix(handleID, "PID:")
pid, err := strconv.Atoi(pidStr)
if err != nil {
return nil, fmt.Errorf("failed to parse handle '%s': %v", handleID, err)
}

// Find the process
proc, err := os.FindProcess(pid)
if proc == nil || err != nil {
return nil, fmt.Errorf("failed to find PID %d: %v", pid, err)
}

// Return a driver handle
h := &rawExecHandle{
proc: proc,
doneCh: make(chan struct{}),
waitCh: make(chan error, 1),
}
go h.run()
return h, nil
}

func (h *rawExecHandle) ID() string {
// Return a handle to the PID
return fmt.Sprintf("PID:%d", h.proc.Pid)
}

func (h *rawExecHandle) WaitCh() chan error {
return h.waitCh
}

func (h *rawExecHandle) Update(task *structs.Task) error {
// Update is not possible
return nil
}

// Kill is used to terminate the task. We send an Interrupt
// and then provide a 5 second grace period before doing a Kill on supported
// OS's, otherwise we kill immediately.
func (h *rawExecHandle) Kill() error {
if runtime.GOOS == "windows" {
return h.proc.Kill()
}

h.proc.Signal(os.Interrupt)
select {
case <-h.doneCh:
return nil
case <-time.After(5 * time.Second):
return h.proc.Kill()
}
}

func (h *rawExecHandle) run() {
ps, err := h.proc.Wait()
close(h.doneCh)
if err != nil {
h.waitCh <- err
} else if !ps.Success() {
h.waitCh <- fmt.Errorf("task exited with error")
}
close(h.waitCh)
}
Loading