github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/exec.go (about) 1 package driver 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log" 8 "os" 9 "path/filepath" 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/driver/executor" 16 dstructs "github.com/hashicorp/nomad/client/driver/structs" 17 cstructs "github.com/hashicorp/nomad/client/structs" 18 "github.com/hashicorp/nomad/helper/fields" 19 "github.com/hashicorp/nomad/nomad/structs" 20 "github.com/mitchellh/mapstructure" 21 ) 22 23 const ( 24 // The key populated in Node Attributes to indicate the presence of the Exec 25 // driver 26 execDriverAttr = "driver.exec" 27 ) 28 29 // ExecDriver fork/execs tasks using as many of the underlying OS's isolation 30 // features. 31 type ExecDriver struct { 32 DriverContext 33 34 // A tri-state boolean to know if the fingerprinting has happened and 35 // whether it has been successful 36 fingerprintSuccess *bool 37 } 38 39 type ExecDriverConfig struct { 40 Command string `mapstructure:"command"` 41 Args []string `mapstructure:"args"` 42 } 43 44 // execHandle is returned from Start/Open as a handle to the PID 45 type execHandle struct { 46 pluginClient *plugin.Client 47 executor executor.Executor 48 isolationConfig *dstructs.IsolationConfig 49 userPid int 50 taskDir *allocdir.TaskDir 51 killTimeout time.Duration 52 maxKillTimeout time.Duration 53 logger *log.Logger 54 waitCh chan *dstructs.WaitResult 55 doneCh chan struct{} 56 version string 57 } 58 59 // NewExecDriver is used to create a new exec driver 60 func NewExecDriver(ctx *DriverContext) Driver { 61 return &ExecDriver{DriverContext: *ctx} 62 } 63 64 // Validate is used to validate the driver configuration 65 func (d *ExecDriver) Validate(config map[string]interface{}) error { 66 fd := &fields.FieldData{ 67 Raw: config, 68 Schema: map[string]*fields.FieldSchema{ 69 "command": &fields.FieldSchema{ 70 Type: fields.TypeString, 71 Required: true, 72 }, 73 "args": &fields.FieldSchema{ 74 Type: fields.TypeArray, 75 }, 76 }, 77 } 78 79 if err := fd.Validate(); err != nil { 80 return err 81 } 82 83 return nil 84 } 85 86 func (d *ExecDriver) Abilities() DriverAbilities { 87 return DriverAbilities{ 88 SendSignals: true, 89 Exec: true, 90 } 91 } 92 93 func (d *ExecDriver) FSIsolation() cstructs.FSIsolation { 94 return cstructs.FSIsolationChroot 95 } 96 97 func (d *ExecDriver) Periodic() (bool, time.Duration) { 98 return true, 15 * time.Second 99 } 100 101 func (d *ExecDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) { 102 return nil, nil 103 } 104 105 func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 106 var driverConfig ExecDriverConfig 107 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 108 return nil, err 109 } 110 111 // Get the command to be ran 112 command := driverConfig.Command 113 if err := validateCommand(command, "args"); err != nil { 114 return nil, err 115 } 116 117 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out") 118 executorConfig := &dstructs.ExecutorConfig{ 119 LogFile: pluginLogFile, 120 LogLevel: d.config.LogLevel, 121 } 122 exec, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 123 if err != nil { 124 return nil, err 125 } 126 executorCtx := &executor.ExecutorContext{ 127 TaskEnv: ctx.TaskEnv, 128 Driver: "exec", 129 AllocID: d.DriverContext.allocID, 130 LogDir: ctx.TaskDir.LogDir, 131 TaskDir: ctx.TaskDir.Dir, 132 Task: task, 133 } 134 if err := exec.SetContext(executorCtx); err != nil { 135 pluginClient.Kill() 136 return nil, fmt.Errorf("failed to set executor context: %v", err) 137 } 138 139 execCmd := &executor.ExecCommand{ 140 Cmd: command, 141 Args: driverConfig.Args, 142 FSIsolation: true, 143 ResourceLimits: true, 144 User: getExecutorUser(task), 145 } 146 147 ps, err := exec.LaunchCmd(execCmd) 148 if err != nil { 149 pluginClient.Kill() 150 return nil, err 151 } 152 153 d.logger.Printf("[DEBUG] driver.exec: started process via plugin with pid: %v", ps.Pid) 154 155 // Return a driver handle 156 maxKill := d.DriverContext.config.MaxKillTimeout 157 h := &execHandle{ 158 pluginClient: pluginClient, 159 userPid: ps.Pid, 160 executor: exec, 161 isolationConfig: ps.IsolationConfig, 162 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 163 maxKillTimeout: maxKill, 164 logger: d.logger, 165 version: d.config.Version, 166 doneCh: make(chan struct{}), 167 waitCh: make(chan *dstructs.WaitResult, 1), 168 taskDir: ctx.TaskDir, 169 } 170 go h.run() 171 return &StartResponse{Handle: h}, nil 172 } 173 174 func (d *ExecDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 175 176 type execId struct { 177 Version string 178 KillTimeout time.Duration 179 MaxKillTimeout time.Duration 180 UserPid int 181 IsolationConfig *dstructs.IsolationConfig 182 PluginConfig *PluginReattachConfig 183 } 184 185 func (d *ExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 186 id := &execId{} 187 if err := json.Unmarshal([]byte(handleID), id); err != nil { 188 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 189 } 190 191 pluginConfig := &plugin.ClientConfig{ 192 Reattach: id.PluginConfig.PluginConfig(), 193 } 194 exec, client, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 195 if err != nil { 196 merrs := new(multierror.Error) 197 merrs.Errors = append(merrs.Errors, err) 198 d.logger.Println("[ERR] driver.exec: error connecting to plugin so destroying plugin pid and user pid") 199 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 200 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 201 } 202 if id.IsolationConfig != nil { 203 ePid := pluginConfig.Reattach.Pid 204 if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil { 205 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying cgroup failed: %v", e)) 206 } 207 } 208 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 209 } 210 211 ver, _ := exec.Version() 212 d.logger.Printf("[DEBUG] driver.exec : version of executor: %v", ver.Version) 213 // Return a driver handle 214 h := &execHandle{ 215 pluginClient: client, 216 executor: exec, 217 userPid: id.UserPid, 218 isolationConfig: id.IsolationConfig, 219 logger: d.logger, 220 version: id.Version, 221 killTimeout: id.KillTimeout, 222 maxKillTimeout: id.MaxKillTimeout, 223 doneCh: make(chan struct{}), 224 waitCh: make(chan *dstructs.WaitResult, 1), 225 taskDir: ctx.TaskDir, 226 } 227 go h.run() 228 return h, nil 229 } 230 231 func (h *execHandle) ID() string { 232 id := execId{ 233 Version: h.version, 234 KillTimeout: h.killTimeout, 235 MaxKillTimeout: h.maxKillTimeout, 236 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 237 UserPid: h.userPid, 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) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 262 deadline, ok := ctx.Deadline() 263 if !ok { 264 // No deadline set on context; default to 1 minute 265 deadline = time.Now().Add(time.Minute) 266 } 267 return h.executor.Exec(deadline, cmd, args) 268 } 269 270 func (h *execHandle) Signal(s os.Signal) error { 271 return h.executor.Signal(s) 272 } 273 274 func (h *execHandle) 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 case <-time.After(h.killTimeout): 285 if h.pluginClient.Exited() { 286 break 287 } 288 if err := h.executor.Exit(); err != nil { 289 return fmt.Errorf("executor Exit failed: %v", err) 290 } 291 } 292 return nil 293 } 294 295 func (h *execHandle) Stats() (*cstructs.TaskResourceUsage, error) { 296 return h.executor.Stats() 297 } 298 299 func (h *execHandle) run() { 300 ps, werr := h.executor.Wait() 301 close(h.doneCh) 302 303 // If the exitcode is 0 and we had an error that means the plugin didn't 304 // connect and doesn't know the state of the user process so we are killing 305 // the user process so that when we create a new executor on restarting the 306 // new user process doesn't have collisions with resources that the older 307 // user pid might be holding onto. 308 if ps.ExitCode == 0 && werr != nil { 309 if h.isolationConfig != nil { 310 ePid := h.pluginClient.ReattachConfig().Pid 311 if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil { 312 h.logger.Printf("[ERR] driver.exec: destroying resource container failed: %v", e) 313 } 314 } 315 } 316 317 // Exit the executor 318 if err := h.executor.Exit(); err != nil { 319 h.logger.Printf("[ERR] driver.exec: error destroying executor: %v", err) 320 } 321 h.pluginClient.Kill() 322 323 // Send the results 324 h.waitCh <- dstructs.NewWaitResult(ps.ExitCode, ps.Signal, werr) 325 close(h.waitCh) 326 }