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 }