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