github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/driver/executor/executor.go (about) 1 package executor 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "sync" 12 "syscall" 13 "time" 14 15 "github.com/hashicorp/go-multierror" 16 cgroupConfig "github.com/opencontainers/runc/libcontainer/configs" 17 18 "github.com/hashicorp/nomad/client/allocdir" 19 "github.com/hashicorp/nomad/client/driver/env" 20 "github.com/hashicorp/nomad/client/driver/logging" 21 cstructs "github.com/hashicorp/nomad/client/driver/structs" 22 "github.com/hashicorp/nomad/nomad/structs" 23 ) 24 25 // ExecutorContext holds context to configure the command user 26 // wants to run and isolate it 27 type ExecutorContext struct { 28 // TaskEnv holds information about the environment of a Task 29 TaskEnv *env.TaskEnvironment 30 31 // AllocDir is the handle to do operations on the alloc dir of 32 // the task 33 AllocDir *allocdir.AllocDir 34 35 // TaskName is the name of the Task 36 TaskName string 37 38 // TaskResources are the resource constraints for the Task 39 TaskResources *structs.Resources 40 41 // FSIsolation is a flag for drivers to impose file system 42 // isolation on certain platforms 43 FSIsolation bool 44 45 // ResourceLimits is a flag for drivers to impose resource 46 // contraints on a Task on certain platforms 47 ResourceLimits bool 48 49 // UnprivilegedUser is a flag for drivers to make the process 50 // run as nobody 51 UnprivilegedUser bool 52 53 // LogConfig provides the configuration related to log rotation 54 LogConfig *structs.LogConfig 55 } 56 57 // ExecCommand holds the user command and args. It's a lightweight replacement 58 // of exec.Cmd for serialization purposes. 59 type ExecCommand struct { 60 Cmd string 61 Args []string 62 } 63 64 // ProcessState holds information about the state of a user process. 65 type ProcessState struct { 66 Pid int 67 ExitCode int 68 Signal int 69 IsolationConfig *cstructs.IsolationConfig 70 Time time.Time 71 } 72 73 // Executor is the interface which allows a driver to launch and supervise 74 // a process 75 type Executor interface { 76 LaunchCmd(command *ExecCommand, ctx *ExecutorContext) (*ProcessState, error) 77 Wait() (*ProcessState, error) 78 ShutDown() error 79 Exit() error 80 UpdateLogConfig(logConfig *structs.LogConfig) error 81 } 82 83 // UniversalExecutor is an implementation of the Executor which launches and 84 // supervises processes. In addition to process supervision it provides resource 85 // and file system isolation 86 type UniversalExecutor struct { 87 cmd exec.Cmd 88 ctx *ExecutorContext 89 90 taskDir string 91 groups *cgroupConfig.Cgroup 92 exitState *ProcessState 93 processExited chan interface{} 94 lre *logging.FileRotator 95 lro *logging.FileRotator 96 97 logger *log.Logger 98 lock sync.Mutex 99 } 100 101 // NewExecutor returns an Executor 102 func NewExecutor(logger *log.Logger) Executor { 103 return &UniversalExecutor{logger: logger, processExited: make(chan interface{})} 104 } 105 106 // LaunchCmd launches a process and returns it's state. It also configures an 107 // applies isolation on certain platforms. 108 func (e *UniversalExecutor) LaunchCmd(command *ExecCommand, ctx *ExecutorContext) (*ProcessState, error) { 109 e.logger.Printf("[DEBUG] executor: launching command %v %v", command.Cmd, strings.Join(command.Args, " ")) 110 111 e.ctx = ctx 112 113 // configuring the task dir 114 if err := e.configureTaskDir(); err != nil { 115 return nil, err 116 } 117 118 // configuring the chroot, cgroup and enters the plugin process in the 119 // chroot 120 if err := e.configureIsolation(); err != nil { 121 return nil, err 122 } 123 124 // setting the user of the process 125 if e.ctx.UnprivilegedUser { 126 if err := e.runAs("nobody"); err != nil { 127 return nil, err 128 } 129 } 130 131 logFileSize := int64(ctx.LogConfig.MaxFileSizeMB * 1024 * 1024) 132 lro, err := logging.NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stdout", ctx.TaskName), 133 ctx.LogConfig.MaxFiles, logFileSize, e.logger) 134 135 if err != nil { 136 return nil, fmt.Errorf("error creating log rotator for stdout of task %v", err) 137 } 138 e.cmd.Stdout = lro 139 e.lro = lro 140 141 lre, err := logging.NewFileRotator(ctx.AllocDir.LogDir(), fmt.Sprintf("%v.stderr", ctx.TaskName), 142 ctx.LogConfig.MaxFiles, logFileSize, e.logger) 143 if err != nil { 144 return nil, fmt.Errorf("error creating log rotator for stderr of task %v", err) 145 } 146 e.cmd.Stderr = lre 147 e.lre = lre 148 149 // setting the env, path and args for the command 150 e.ctx.TaskEnv.Build() 151 e.cmd.Env = ctx.TaskEnv.EnvList() 152 e.cmd.Path = ctx.TaskEnv.ReplaceEnv(command.Cmd) 153 e.cmd.Args = append([]string{e.cmd.Path}, ctx.TaskEnv.ParseAndReplace(command.Args)...) 154 if filepath.Base(command.Cmd) == command.Cmd { 155 if lp, err := exec.LookPath(command.Cmd); err != nil { 156 } else { 157 e.cmd.Path = lp 158 } 159 } 160 161 // starting the process 162 if err := e.cmd.Start(); err != nil { 163 return nil, fmt.Errorf("error starting command: %v", err) 164 } 165 go e.wait() 166 ic := &cstructs.IsolationConfig{Cgroup: e.groups} 167 return &ProcessState{Pid: e.cmd.Process.Pid, ExitCode: -1, IsolationConfig: ic, Time: time.Now()}, nil 168 } 169 170 // Wait waits until a process has exited and returns it's exitcode and errors 171 func (e *UniversalExecutor) Wait() (*ProcessState, error) { 172 <-e.processExited 173 return e.exitState, nil 174 } 175 176 // UpdateLogConfig updates the log configuration 177 func (e *UniversalExecutor) UpdateLogConfig(logConfig *structs.LogConfig) error { 178 e.ctx.LogConfig = logConfig 179 if e.lro == nil { 180 return fmt.Errorf("log rotator for stdout doesn't exist") 181 } 182 e.lro.MaxFiles = logConfig.MaxFiles 183 e.lro.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024) 184 185 if e.lre == nil { 186 return fmt.Errorf("log rotator for stderr doesn't exist") 187 } 188 e.lre.MaxFiles = logConfig.MaxFiles 189 e.lre.FileSize = int64(logConfig.MaxFileSizeMB * 1024 * 1024) 190 return nil 191 } 192 193 func (e *UniversalExecutor) wait() { 194 defer close(e.processExited) 195 err := e.cmd.Wait() 196 e.lre.Close() 197 e.lro.Close() 198 if err == nil { 199 e.exitState = &ProcessState{Pid: 0, ExitCode: 0, Time: time.Now()} 200 return 201 } 202 exitCode := 1 203 if exitErr, ok := err.(*exec.ExitError); ok { 204 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 205 exitCode = status.ExitStatus() 206 } 207 } 208 if e.ctx.FSIsolation { 209 e.removeChrootMounts() 210 } 211 if e.ctx.ResourceLimits { 212 e.lock.Lock() 213 DestroyCgroup(e.groups) 214 e.lock.Unlock() 215 } 216 e.exitState = &ProcessState{Pid: 0, ExitCode: exitCode, Time: time.Now()} 217 } 218 219 var ( 220 // finishedErr is the error message received when trying to kill and already 221 // exited process. 222 finishedErr = "os: process already finished" 223 ) 224 225 // Exit cleans up the alloc directory, destroys cgroups and kills the user 226 // process 227 func (e *UniversalExecutor) Exit() error { 228 var merr multierror.Error 229 if e.cmd.Process != nil { 230 proc, err := os.FindProcess(e.cmd.Process.Pid) 231 if err != nil { 232 e.logger.Printf("[ERR] executor: can't find process with pid: %v, err: %v", 233 e.cmd.Process.Pid, err) 234 } else if err := proc.Kill(); err != nil && err.Error() != finishedErr { 235 merr.Errors = append(merr.Errors, 236 fmt.Errorf("can't kill process with pid: %v, err: %v", e.cmd.Process.Pid, err)) 237 } 238 } 239 240 if e.ctx.FSIsolation { 241 if err := e.removeChrootMounts(); err != nil { 242 merr.Errors = append(merr.Errors, err) 243 } 244 } 245 if e.ctx.ResourceLimits { 246 e.lock.Lock() 247 if err := DestroyCgroup(e.groups); err != nil { 248 merr.Errors = append(merr.Errors, err) 249 } 250 e.lock.Unlock() 251 } 252 return merr.ErrorOrNil() 253 } 254 255 // Shutdown sends an interrupt signal to the user process 256 func (e *UniversalExecutor) ShutDown() error { 257 if e.cmd.Process == nil { 258 return fmt.Errorf("executor.shutdown error: no process found") 259 } 260 proc, err := os.FindProcess(e.cmd.Process.Pid) 261 if err != nil { 262 return fmt.Errorf("executor.shutdown error: %v", err) 263 } 264 if runtime.GOOS == "windows" { 265 return proc.Kill() 266 } 267 if err = proc.Signal(os.Interrupt); err != nil { 268 return fmt.Errorf("executor.shutdown error: %v", err) 269 } 270 return nil 271 } 272 273 // configureTaskDir sets the task dir in the executor 274 func (e *UniversalExecutor) configureTaskDir() error { 275 taskDir, ok := e.ctx.AllocDir.TaskDirs[e.ctx.TaskName] 276 e.taskDir = taskDir 277 if !ok { 278 return fmt.Errorf("couldn't find task directory for task %v", e.ctx.TaskName) 279 } 280 e.cmd.Dir = taskDir 281 return nil 282 }