github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/driver/exec.go (about) 1 package driver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "os/exec" 8 "path/filepath" 9 "syscall" 10 "time" 11 12 "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/go-plugin" 14 "github.com/hashicorp/nomad/client/allocdir" 15 "github.com/hashicorp/nomad/client/config" 16 "github.com/hashicorp/nomad/client/driver/executor" 17 cstructs "github.com/hashicorp/nomad/client/driver/structs" 18 "github.com/hashicorp/nomad/client/getter" 19 "github.com/hashicorp/nomad/helper/discover" 20 "github.com/hashicorp/nomad/nomad/structs" 21 "github.com/mitchellh/mapstructure" 22 ) 23 24 // ExecDriver fork/execs tasks using as many of the underlying OS's isolation 25 // features. 26 type ExecDriver struct { 27 DriverContext 28 } 29 30 type ExecDriverConfig struct { 31 ArtifactSource string `mapstructure:"artifact_source"` 32 Checksum string `mapstructure:"checksum"` 33 Command string `mapstructure:"command"` 34 Args []string `mapstructure:"args"` 35 } 36 37 // execHandle is returned from Start/Open as a handle to the PID 38 type execHandle struct { 39 pluginClient *plugin.Client 40 executor executor.Executor 41 isolationConfig *cstructs.IsolationConfig 42 userPid int 43 allocDir *allocdir.AllocDir 44 killTimeout time.Duration 45 logger *log.Logger 46 waitCh chan *cstructs.WaitResult 47 doneCh chan struct{} 48 version string 49 } 50 51 // NewExecDriver is used to create a new exec driver 52 func NewExecDriver(ctx *DriverContext) Driver { 53 return &ExecDriver{DriverContext: *ctx} 54 } 55 56 func (d *ExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 57 // Only enable if cgroups are available and we are root 58 if _, ok := node.Attributes["unique.cgroup.mountpoint"]; !ok { 59 d.logger.Printf("[DEBUG] driver.exec: cgroups unavailable, disabling") 60 return false, nil 61 } else if syscall.Geteuid() != 0 { 62 d.logger.Printf("[DEBUG] driver.exec: must run as root user, disabling") 63 return false, nil 64 } 65 66 node.Attributes["driver.exec"] = "1" 67 return true, nil 68 } 69 70 func (d *ExecDriver) Periodic() (bool, time.Duration) { 71 return true, 15 * time.Second 72 } 73 74 func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 75 var driverConfig ExecDriverConfig 76 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 77 return nil, err 78 } 79 // Get the command to be ran 80 command := driverConfig.Command 81 if err := validateCommand(command, "args"); err != nil { 82 return nil, err 83 } 84 85 // Create a location to download the artifact. 86 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 87 if !ok { 88 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 89 } 90 91 // Check if an artificat is specified and attempt to download it 92 source, ok := task.Config["artifact_source"] 93 if ok && source != "" { 94 // Proceed to download an artifact to be executed. 95 _, err := getter.GetArtifact( 96 taskDir, 97 driverConfig.ArtifactSource, 98 driverConfig.Checksum, 99 d.logger, 100 ) 101 if err != nil { 102 return nil, err 103 } 104 } 105 106 bin, err := discover.NomadExecutable() 107 if err != nil { 108 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 109 } 110 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 111 pluginConfig := &plugin.ClientConfig{ 112 Cmd: exec.Command(bin, "executor", pluginLogFile), 113 } 114 115 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 116 if err != nil { 117 return nil, err 118 } 119 executorCtx := &executor.ExecutorContext{ 120 TaskEnv: d.taskEnv, 121 AllocDir: ctx.AllocDir, 122 TaskName: task.Name, 123 TaskResources: task.Resources, 124 LogConfig: task.LogConfig, 125 ResourceLimits: true, 126 FSIsolation: true, 127 UnprivilegedUser: true, 128 } 129 ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: command, Args: driverConfig.Args}, executorCtx) 130 if err != nil { 131 pluginClient.Kill() 132 return nil, fmt.Errorf("error starting process via the plugin: %v", err) 133 } 134 d.logger.Printf("[DEBUG] driver.exec: started process via plugin with pid: %v", ps.Pid) 135 136 // Return a driver handle 137 h := &execHandle{ 138 pluginClient: pluginClient, 139 userPid: ps.Pid, 140 executor: exec, 141 allocDir: ctx.AllocDir, 142 isolationConfig: ps.IsolationConfig, 143 killTimeout: d.DriverContext.KillTimeout(task), 144 logger: d.logger, 145 version: d.config.Version, 146 doneCh: make(chan struct{}), 147 waitCh: make(chan *cstructs.WaitResult, 1), 148 } 149 go h.run() 150 return h, nil 151 } 152 153 type execId struct { 154 Version string 155 KillTimeout time.Duration 156 UserPid int 157 TaskDir string 158 AllocDir *allocdir.AllocDir 159 IsolationConfig *cstructs.IsolationConfig 160 PluginConfig *PluginReattachConfig 161 } 162 163 func (d *ExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 164 id := &execId{} 165 if err := json.Unmarshal([]byte(handleID), id); err != nil { 166 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 167 } 168 169 pluginConfig := &plugin.ClientConfig{ 170 Reattach: id.PluginConfig.PluginConfig(), 171 } 172 exec, client, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 173 if err != nil { 174 merrs := new(multierror.Error) 175 merrs.Errors = append(merrs.Errors, err) 176 d.logger.Println("[ERR] driver.exec: error connecting to plugin so destroying plugin pid and user pid") 177 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 178 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 179 } 180 if id.IsolationConfig != nil { 181 if e := executor.DestroyCgroup(id.IsolationConfig.Cgroup); e != nil { 182 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying cgroup failed: %v", e)) 183 } 184 } 185 if e := ctx.AllocDir.UnmountAll(); e != nil { 186 merrs.Errors = append(merrs.Errors, e) 187 } 188 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 189 } 190 191 // Return a driver handle 192 h := &execHandle{ 193 pluginClient: client, 194 executor: exec, 195 userPid: id.UserPid, 196 allocDir: id.AllocDir, 197 isolationConfig: id.IsolationConfig, 198 logger: d.logger, 199 version: id.Version, 200 killTimeout: id.KillTimeout, 201 doneCh: make(chan struct{}), 202 waitCh: make(chan *cstructs.WaitResult, 1), 203 } 204 go h.run() 205 return h, nil 206 } 207 208 func (h *execHandle) ID() string { 209 id := execId{ 210 Version: h.version, 211 KillTimeout: h.killTimeout, 212 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 213 UserPid: h.userPid, 214 AllocDir: h.allocDir, 215 IsolationConfig: h.isolationConfig, 216 } 217 218 data, err := json.Marshal(id) 219 if err != nil { 220 h.logger.Printf("[ERR] driver.exec: failed to marshal ID to JSON: %s", err) 221 } 222 return string(data) 223 } 224 225 func (h *execHandle) WaitCh() chan *cstructs.WaitResult { 226 return h.waitCh 227 } 228 229 func (h *execHandle) Update(task *structs.Task) error { 230 // Store the updated kill timeout. 231 h.killTimeout = task.KillTimeout 232 h.executor.UpdateLogConfig(task.LogConfig) 233 234 // Update is not possible 235 return nil 236 } 237 238 func (h *execHandle) Kill() error { 239 if err := h.executor.ShutDown(); err != nil { 240 if h.pluginClient.Exited() { 241 return nil 242 } 243 return fmt.Errorf("executor Shutdown failed: %v", err) 244 } 245 246 select { 247 case <-h.doneCh: 248 return nil 249 case <-time.After(h.killTimeout): 250 if h.pluginClient.Exited() { 251 return nil 252 } 253 if err := h.executor.Exit(); err != nil { 254 return fmt.Errorf("executor Exit failed: %v", err) 255 } 256 257 return nil 258 } 259 } 260 261 func (h *execHandle) run() { 262 ps, err := h.executor.Wait() 263 close(h.doneCh) 264 265 // If the exitcode is 0 and we had an error that means the plugin didn't 266 // connect and doesn't know the state of the user process so we are killing 267 // the user process so that when we create a new executor on restarting the 268 // new user process doesn't have collisions with resources that the older 269 // user pid might be holding onto. 270 if ps.ExitCode == 0 && err != nil { 271 if h.isolationConfig != nil { 272 if e := executor.DestroyCgroup(h.isolationConfig.Cgroup); e != nil { 273 h.logger.Printf("[ERR] driver.exec: destroying cgroup failed while killing cgroup: %v", e) 274 } 275 } 276 if e := h.allocDir.UnmountAll(); e != nil { 277 h.logger.Printf("[ERR] driver.exec: unmounting dev,proc and alloc dirs failed: %v", e) 278 } 279 } 280 h.waitCh <- &cstructs.WaitResult{ExitCode: ps.ExitCode, Signal: 0, 281 Err: err} 282 close(h.waitCh) 283 h.pluginClient.Kill() 284 }