github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/raw_exec.go (about) 1 package driver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "time" 12 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 dstructs "github.com/hashicorp/nomad/client/driver/structs" 18 "github.com/hashicorp/nomad/client/fingerprint" 19 cstructs "github.com/hashicorp/nomad/client/structs" 20 "github.com/hashicorp/nomad/helper/discover" 21 "github.com/hashicorp/nomad/helper/fields" 22 "github.com/hashicorp/nomad/nomad/structs" 23 "github.com/mitchellh/mapstructure" 24 ) 25 26 const ( 27 // The option that enables this driver in the Config.Options map. 28 rawExecConfigOption = "driver.raw_exec.enable" 29 30 // The key populated in Node Attributes to indicate presence of the Raw Exec 31 // driver 32 rawExecDriverAttr = "driver.raw_exec" 33 ) 34 35 // The RawExecDriver is a privileged version of the exec driver. It provides no 36 // resource isolation and just fork/execs. The Exec driver should be preferred 37 // and this should only be used when explicitly needed. 38 type RawExecDriver struct { 39 DriverContext 40 fingerprint.StaticFingerprinter 41 } 42 43 // rawExecHandle is returned from Start/Open as a handle to the PID 44 type rawExecHandle struct { 45 version string 46 pluginClient *plugin.Client 47 userPid int 48 executor executor.Executor 49 killTimeout time.Duration 50 maxKillTimeout time.Duration 51 allocDir *allocdir.AllocDir 52 logger *log.Logger 53 waitCh chan *dstructs.WaitResult 54 doneCh chan struct{} 55 } 56 57 // NewRawExecDriver is used to create a new raw exec driver 58 func NewRawExecDriver(ctx *DriverContext) Driver { 59 return &RawExecDriver{DriverContext: *ctx} 60 } 61 62 // Validate is used to validate the driver configuration 63 func (d *RawExecDriver) Validate(config map[string]interface{}) error { 64 fd := &fields.FieldData{ 65 Raw: config, 66 Schema: map[string]*fields.FieldSchema{ 67 "command": &fields.FieldSchema{ 68 Type: fields.TypeString, 69 Required: true, 70 }, 71 "args": &fields.FieldSchema{ 72 Type: fields.TypeArray, 73 }, 74 }, 75 } 76 77 if err := fd.Validate(); err != nil { 78 return err 79 } 80 81 return nil 82 } 83 84 func (d *RawExecDriver) Abilities() DriverAbilities { 85 return DriverAbilities{ 86 SendSignals: true, 87 } 88 } 89 90 func (d *RawExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 91 // Get the current status so that we can log any debug messages only if the 92 // state changes 93 _, currentlyEnabled := node.Attributes[rawExecDriverAttr] 94 95 // Check that the user has explicitly enabled this executor. 96 enabled := cfg.ReadBoolDefault(rawExecConfigOption, false) 97 98 if enabled || cfg.DevMode { 99 if currentlyEnabled { 100 d.logger.Printf("[WARN] driver.raw_exec: raw exec is enabled. Only enable if needed") 101 } 102 node.Attributes[rawExecDriverAttr] = "1" 103 return true, nil 104 } 105 106 delete(node.Attributes, rawExecDriverAttr) 107 return false, nil 108 } 109 110 func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 111 var driverConfig ExecDriverConfig 112 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 113 return nil, err 114 } 115 // Get the tasks local directory. 116 taskName := d.DriverContext.taskName 117 taskDir, ok := ctx.AllocDir.TaskDirs[taskName] 118 if !ok { 119 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 120 } 121 122 // Get the command to be ran 123 command := driverConfig.Command 124 if err := validateCommand(command, "args"); err != nil { 125 return nil, err 126 } 127 128 // Set the host environment variables. 129 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 130 d.taskEnv.AppendHostEnvvars(filter) 131 132 bin, err := discover.NomadExecutable() 133 if err != nil { 134 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 135 } 136 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 137 pluginConfig := &plugin.ClientConfig{ 138 Cmd: exec.Command(bin, "executor", pluginLogFile), 139 } 140 141 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 142 if err != nil { 143 return nil, err 144 } 145 executorCtx := &executor.ExecutorContext{ 146 TaskEnv: d.taskEnv, 147 Driver: "raw_exec", 148 AllocDir: ctx.AllocDir, 149 AllocID: ctx.AllocID, 150 Task: task, 151 } 152 if err := exec.SetContext(executorCtx); err != nil { 153 pluginClient.Kill() 154 return nil, fmt.Errorf("failed to set executor context: %v", err) 155 } 156 157 execCmd := &executor.ExecCommand{ 158 Cmd: command, 159 Args: driverConfig.Args, 160 User: task.User, 161 } 162 ps, err := exec.LaunchCmd(execCmd) 163 if err != nil { 164 pluginClient.Kill() 165 return nil, err 166 } 167 d.logger.Printf("[DEBUG] driver.raw_exec: started process with pid: %v", ps.Pid) 168 169 // Return a driver handle 170 maxKill := d.DriverContext.config.MaxKillTimeout 171 h := &rawExecHandle{ 172 pluginClient: pluginClient, 173 executor: exec, 174 userPid: ps.Pid, 175 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 176 maxKillTimeout: maxKill, 177 allocDir: ctx.AllocDir, 178 version: d.config.Version, 179 logger: d.logger, 180 doneCh: make(chan struct{}), 181 waitCh: make(chan *dstructs.WaitResult, 1), 182 } 183 if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { 184 h.logger.Printf("[ERR] driver.raw_exec: error registering services with consul for task: %q: %v", task.Name, err) 185 } 186 go h.run() 187 return h, nil 188 } 189 190 type rawExecId struct { 191 Version string 192 KillTimeout time.Duration 193 MaxKillTimeout time.Duration 194 UserPid int 195 PluginConfig *PluginReattachConfig 196 AllocDir *allocdir.AllocDir 197 } 198 199 func (d *RawExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 200 id := &rawExecId{} 201 if err := json.Unmarshal([]byte(handleID), id); err != nil { 202 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 203 } 204 205 pluginConfig := &plugin.ClientConfig{ 206 Reattach: id.PluginConfig.PluginConfig(), 207 } 208 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 209 if err != nil { 210 d.logger.Println("[ERR] driver.raw_exec: error connecting to plugin so destroying plugin pid and user pid") 211 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 212 d.logger.Printf("[ERR] driver.raw_exec: error destroying plugin and userpid: %v", e) 213 } 214 return nil, fmt.Errorf("error connecting to plugin: %v", err) 215 } 216 217 ver, _ := exec.Version() 218 d.logger.Printf("[DEBUG] driver.raw_exec: version of executor: %v", ver.Version) 219 220 // Return a driver handle 221 h := &rawExecHandle{ 222 pluginClient: pluginClient, 223 executor: exec, 224 userPid: id.UserPid, 225 logger: d.logger, 226 killTimeout: id.KillTimeout, 227 maxKillTimeout: id.MaxKillTimeout, 228 allocDir: id.AllocDir, 229 version: id.Version, 230 doneCh: make(chan struct{}), 231 waitCh: make(chan *dstructs.WaitResult, 1), 232 } 233 if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { 234 h.logger.Printf("[ERR] driver.raw_exec: error registering services with consul: %v", err) 235 } 236 go h.run() 237 return h, nil 238 } 239 240 func (h *rawExecHandle) ID() string { 241 id := rawExecId{ 242 Version: h.version, 243 KillTimeout: h.killTimeout, 244 MaxKillTimeout: h.maxKillTimeout, 245 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 246 UserPid: h.userPid, 247 AllocDir: h.allocDir, 248 } 249 250 data, err := json.Marshal(id) 251 if err != nil { 252 h.logger.Printf("[ERR] driver.raw_exec: failed to marshal ID to JSON: %s", err) 253 } 254 return string(data) 255 } 256 257 func (h *rawExecHandle) WaitCh() chan *dstructs.WaitResult { 258 return h.waitCh 259 } 260 261 func (h *rawExecHandle) Update(task *structs.Task) error { 262 // Store the updated kill timeout. 263 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 264 h.executor.UpdateTask(task) 265 266 // Update is not possible 267 return nil 268 } 269 270 func (h *rawExecHandle) Signal(s os.Signal) error { 271 return h.executor.Signal(s) 272 } 273 274 func (h *rawExecHandle) Kill() error { 275 if err := h.executor.ShutDown(); err != nil { 276 if h.pluginClient.Exited() { 277 return nil 278 } 279 return fmt.Errorf("executor Shutdown failed: %v", err) 280 } 281 282 select { 283 case <-h.doneCh: 284 return nil 285 case <-time.After(h.killTimeout): 286 if h.pluginClient.Exited() { 287 return nil 288 } 289 if err := h.executor.Exit(); err != nil { 290 return fmt.Errorf("executor Exit failed: %v", err) 291 } 292 293 return nil 294 } 295 } 296 297 func (h *rawExecHandle) Stats() (*cstructs.TaskResourceUsage, error) { 298 return h.executor.Stats() 299 } 300 301 func (h *rawExecHandle) run() { 302 ps, werr := h.executor.Wait() 303 close(h.doneCh) 304 if ps.ExitCode == 0 && werr != nil { 305 if e := killProcess(h.userPid); e != nil { 306 h.logger.Printf("[ERR] driver.raw_exec: error killing user process: %v", e) 307 } 308 if e := h.allocDir.UnmountAll(); e != nil { 309 h.logger.Printf("[ERR] driver.raw_exec: unmounting dev,proc and alloc dirs failed: %v", e) 310 } 311 } 312 // Remove services 313 if err := h.executor.DeregisterServices(); err != nil { 314 h.logger.Printf("[ERR] driver.raw_exec: failed to deregister services: %v", err) 315 } 316 317 // Exit the executor 318 if err := h.executor.Exit(); err != nil { 319 h.logger.Printf("[ERR] driver.raw_exec: error killing executor: %v", err) 320 } 321 h.pluginClient.Kill() 322 323 // Send the results 324 h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: werr} 325 close(h.waitCh) 326 }