github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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 "strings" 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 dstructs "github.com/hashicorp/nomad/client/driver/structs" 18 cstructs "github.com/hashicorp/nomad/client/structs" 19 "github.com/hashicorp/nomad/helper/discover" 20 "github.com/hashicorp/nomad/helper/fields" 21 "github.com/hashicorp/nomad/nomad/structs" 22 "github.com/mitchellh/mapstructure" 23 ) 24 25 const ( 26 // The key populated in Node Attributes to indicate the presence of the Exec 27 // driver 28 execDriverAttr = "driver.exec" 29 ) 30 31 // ExecDriver fork/execs tasks using as many of the underlying OS's isolation 32 // features. 33 type ExecDriver struct { 34 DriverContext 35 } 36 37 type ExecDriverConfig struct { 38 Command string `mapstructure:"command"` 39 Args []string `mapstructure:"args"` 40 } 41 42 // execHandle is returned from Start/Open as a handle to the PID 43 type execHandle struct { 44 pluginClient *plugin.Client 45 executor executor.Executor 46 isolationConfig *dstructs.IsolationConfig 47 userPid int 48 allocDir *allocdir.AllocDir 49 killTimeout time.Duration 50 maxKillTimeout time.Duration 51 logger *log.Logger 52 waitCh chan *dstructs.WaitResult 53 doneCh chan struct{} 54 version string 55 } 56 57 // NewExecDriver is used to create a new exec driver 58 func NewExecDriver(ctx *DriverContext) Driver { 59 return &ExecDriver{DriverContext: *ctx} 60 } 61 62 // Validate is used to validate the driver configuration 63 func (d *ExecDriver) 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 *ExecDriver) Periodic() (bool, time.Duration) { 85 return true, 15 * time.Second 86 } 87 88 func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 89 var driverConfig ExecDriverConfig 90 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 91 return nil, err 92 } 93 94 // Get the command to be ran 95 command := driverConfig.Command 96 if err := validateCommand(command, "args"); err != nil { 97 return nil, err 98 } 99 100 // Set the host environment variables. 101 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 102 d.taskEnv.AppendHostEnvvars(filter) 103 104 // Get the task directory for storing the executor logs. 105 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 106 if !ok { 107 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 108 } 109 110 bin, err := discover.NomadExecutable() 111 if err != nil { 112 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 113 } 114 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 115 pluginConfig := &plugin.ClientConfig{ 116 Cmd: exec.Command(bin, "executor", pluginLogFile), 117 } 118 119 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 120 if err != nil { 121 return nil, err 122 } 123 executorCtx := &executor.ExecutorContext{ 124 TaskEnv: d.taskEnv, 125 Driver: "exec", 126 AllocDir: ctx.AllocDir, 127 AllocID: ctx.AllocID, 128 ChrootEnv: d.config.ChrootEnv, 129 Task: task, 130 } 131 132 ps, err := exec.LaunchCmd(&executor.ExecCommand{ 133 Cmd: command, 134 Args: driverConfig.Args, 135 FSIsolation: true, 136 ResourceLimits: true, 137 User: getExecutorUser(task), 138 }, executorCtx) 139 if err != nil { 140 pluginClient.Kill() 141 return nil, err 142 } 143 d.logger.Printf("[DEBUG] driver.exec: started process via plugin with pid: %v", ps.Pid) 144 145 // Return a driver handle 146 maxKill := d.DriverContext.config.MaxKillTimeout 147 h := &execHandle{ 148 pluginClient: pluginClient, 149 userPid: ps.Pid, 150 executor: exec, 151 allocDir: ctx.AllocDir, 152 isolationConfig: ps.IsolationConfig, 153 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 154 maxKillTimeout: maxKill, 155 logger: d.logger, 156 version: d.config.Version, 157 doneCh: make(chan struct{}), 158 waitCh: make(chan *dstructs.WaitResult, 1), 159 } 160 if err := exec.SyncServices(consulContext(d.config, "")); err != nil { 161 d.logger.Printf("[ERR] driver.exec: error registering services with consul for task: %q: %v", task.Name, err) 162 } 163 go h.run() 164 return h, nil 165 } 166 167 type execId struct { 168 Version string 169 KillTimeout time.Duration 170 MaxKillTimeout time.Duration 171 UserPid int 172 TaskDir string 173 AllocDir *allocdir.AllocDir 174 IsolationConfig *dstructs.IsolationConfig 175 PluginConfig *PluginReattachConfig 176 } 177 178 func (d *ExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 179 id := &execId{} 180 if err := json.Unmarshal([]byte(handleID), id); err != nil { 181 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 182 } 183 184 pluginConfig := &plugin.ClientConfig{ 185 Reattach: id.PluginConfig.PluginConfig(), 186 } 187 exec, client, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 188 if err != nil { 189 merrs := new(multierror.Error) 190 merrs.Errors = append(merrs.Errors, err) 191 d.logger.Println("[ERR] driver.exec: error connecting to plugin so destroying plugin pid and user pid") 192 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 193 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 194 } 195 if id.IsolationConfig != nil { 196 ePid := pluginConfig.Reattach.Pid 197 if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil { 198 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying cgroup failed: %v", e)) 199 } 200 } 201 if e := ctx.AllocDir.UnmountAll(); e != nil { 202 merrs.Errors = append(merrs.Errors, e) 203 } 204 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 205 } 206 207 ver, _ := exec.Version() 208 d.logger.Printf("[DEBUG] driver.exec : version of executor: %v", ver.Version) 209 // Return a driver handle 210 h := &execHandle{ 211 pluginClient: client, 212 executor: exec, 213 userPid: id.UserPid, 214 allocDir: id.AllocDir, 215 isolationConfig: id.IsolationConfig, 216 logger: d.logger, 217 version: id.Version, 218 killTimeout: id.KillTimeout, 219 maxKillTimeout: id.MaxKillTimeout, 220 doneCh: make(chan struct{}), 221 waitCh: make(chan *dstructs.WaitResult, 1), 222 } 223 if err := exec.SyncServices(consulContext(d.config, "")); err != nil { 224 d.logger.Printf("[ERR] driver.exec: error registering services with consul: %v", err) 225 } 226 go h.run() 227 return h, nil 228 } 229 230 func (h *execHandle) ID() string { 231 id := execId{ 232 Version: h.version, 233 KillTimeout: h.killTimeout, 234 MaxKillTimeout: h.maxKillTimeout, 235 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 236 UserPid: h.userPid, 237 AllocDir: h.allocDir, 238 IsolationConfig: h.isolationConfig, 239 } 240 241 data, err := json.Marshal(id) 242 if err != nil { 243 h.logger.Printf("[ERR] driver.exec: failed to marshal ID to JSON: %s", err) 244 } 245 return string(data) 246 } 247 248 func (h *execHandle) WaitCh() chan *dstructs.WaitResult { 249 return h.waitCh 250 } 251 252 func (h *execHandle) Update(task *structs.Task) error { 253 // Store the updated kill timeout. 254 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 255 h.executor.UpdateTask(task) 256 257 // Update is not possible 258 return nil 259 } 260 261 func (h *execHandle) Kill() error { 262 if err := h.executor.ShutDown(); err != nil { 263 if h.pluginClient.Exited() { 264 return nil 265 } 266 return fmt.Errorf("executor Shutdown failed: %v", err) 267 } 268 269 select { 270 case <-h.doneCh: 271 return nil 272 case <-time.After(h.killTimeout): 273 if h.pluginClient.Exited() { 274 return nil 275 } 276 if err := h.executor.Exit(); err != nil { 277 return fmt.Errorf("executor Exit failed: %v", err) 278 } 279 280 return nil 281 } 282 } 283 284 func (h *execHandle) Stats() (*cstructs.TaskResourceUsage, error) { 285 return h.executor.Stats() 286 } 287 288 func (h *execHandle) run() { 289 ps, err := h.executor.Wait() 290 close(h.doneCh) 291 292 // If the exitcode is 0 and we had an error that means the plugin didn't 293 // connect and doesn't know the state of the user process so we are killing 294 // the user process so that when we create a new executor on restarting the 295 // new user process doesn't have collisions with resources that the older 296 // user pid might be holding onto. 297 if ps.ExitCode == 0 && err != nil { 298 if h.isolationConfig != nil { 299 ePid := h.pluginClient.ReattachConfig().Pid 300 if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil { 301 h.logger.Printf("[ERR] driver.exec: destroying resource container failed: %v", e) 302 } 303 } 304 if e := h.allocDir.UnmountAll(); e != nil { 305 h.logger.Printf("[ERR] driver.exec: unmounting dev,proc and alloc dirs failed: %v", e) 306 } 307 } 308 h.waitCh <- dstructs.NewWaitResult(ps.ExitCode, ps.Signal, err) 309 close(h.waitCh) 310 // Remove services 311 if err := h.executor.DeregisterServices(); err != nil { 312 h.logger.Printf("[ERR] driver.exec: failed to deregister services: %v", err) 313 } 314 315 if err := h.executor.Exit(); err != nil { 316 h.logger.Printf("[ERR] driver.exec: error destroying executor: %v", err) 317 } 318 h.pluginClient.Kill() 319 }