github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/exec.go (about) 1 package driver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/go-plugin" 13 "github.com/ncodes/nomad/client/allocdir" 14 "github.com/ncodes/nomad/client/driver/executor" 15 dstructs "github.com/ncodes/nomad/client/driver/structs" 16 cstructs "github.com/ncodes/nomad/client/structs" 17 "github.com/ncodes/nomad/helper/fields" 18 "github.com/ncodes/nomad/nomad/structs" 19 "github.com/mitchellh/mapstructure" 20 ) 21 22 const ( 23 // The key populated in Node Attributes to indicate the presence of the Exec 24 // driver 25 execDriverAttr = "driver.exec" 26 ) 27 28 // ExecDriver fork/execs tasks using as many of the underlying OS's isolation 29 // features. 30 type ExecDriver struct { 31 DriverContext 32 33 // A tri-state boolean to know if the fingerprinting has happened and 34 // whether it has been successful 35 fingerprintSuccess *bool 36 } 37 38 type ExecDriverConfig struct { 39 Command string `mapstructure:"command"` 40 Args []string `mapstructure:"args"` 41 } 42 43 // execHandle is returned from Start/Open as a handle to the PID 44 type execHandle struct { 45 pluginClient *plugin.Client 46 executor executor.Executor 47 isolationConfig *dstructs.IsolationConfig 48 userPid int 49 taskDir *allocdir.TaskDir 50 killTimeout time.Duration 51 maxKillTimeout time.Duration 52 logger *log.Logger 53 waitCh chan *dstructs.WaitResult 54 doneCh chan struct{} 55 version string 56 } 57 58 // NewExecDriver is used to create a new exec driver 59 func NewExecDriver(ctx *DriverContext) Driver { 60 return &ExecDriver{DriverContext: *ctx} 61 } 62 63 // Validate is used to validate the driver configuration 64 func (d *ExecDriver) Validate(config map[string]interface{}) error { 65 fd := &fields.FieldData{ 66 Raw: config, 67 Schema: map[string]*fields.FieldSchema{ 68 "command": &fields.FieldSchema{ 69 Type: fields.TypeString, 70 Required: true, 71 }, 72 "args": &fields.FieldSchema{ 73 Type: fields.TypeArray, 74 }, 75 }, 76 } 77 78 if err := fd.Validate(); err != nil { 79 return err 80 } 81 82 return nil 83 } 84 85 func (d *ExecDriver) Abilities() DriverAbilities { 86 return DriverAbilities{ 87 SendSignals: true, 88 } 89 } 90 91 func (d *ExecDriver) FSIsolation() cstructs.FSIsolation { 92 return cstructs.FSIsolationChroot 93 } 94 95 func (d *ExecDriver) Periodic() (bool, time.Duration) { 96 return true, 15 * time.Second 97 } 98 99 func (d *ExecDriver) Prestart(*ExecContext, *structs.Task) (*CreatedResources, error) { 100 return nil, nil 101 } 102 103 func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 104 var driverConfig ExecDriverConfig 105 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 106 return nil, err 107 } 108 109 // Get the command to be ran 110 command := driverConfig.Command 111 if err := validateCommand(command, "args"); err != nil { 112 return nil, err 113 } 114 115 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out") 116 executorConfig := &dstructs.ExecutorConfig{ 117 LogFile: pluginLogFile, 118 LogLevel: d.config.LogLevel, 119 } 120 exec, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 121 if err != nil { 122 return nil, err 123 } 124 executorCtx := &executor.ExecutorContext{ 125 TaskEnv: d.taskEnv, 126 Driver: "exec", 127 AllocID: d.DriverContext.allocID, 128 LogDir: ctx.TaskDir.LogDir, 129 TaskDir: ctx.TaskDir.Dir, 130 Task: task, 131 } 132 if err := exec.SetContext(executorCtx); err != nil { 133 pluginClient.Kill() 134 return nil, fmt.Errorf("failed to set executor context: %v", err) 135 } 136 137 execCmd := &executor.ExecCommand{ 138 Cmd: command, 139 Args: driverConfig.Args, 140 FSIsolation: true, 141 ResourceLimits: true, 142 User: getExecutorUser(task), 143 } 144 145 ps, err := exec.LaunchCmd(execCmd) 146 if err != nil { 147 pluginClient.Kill() 148 return nil, err 149 } 150 151 d.logger.Printf("[DEBUG] driver.exec: started process via plugin with pid: %v", ps.Pid) 152 153 // Return a driver handle 154 maxKill := d.DriverContext.config.MaxKillTimeout 155 h := &execHandle{ 156 pluginClient: pluginClient, 157 userPid: ps.Pid, 158 executor: exec, 159 isolationConfig: ps.IsolationConfig, 160 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 161 maxKillTimeout: maxKill, 162 logger: d.logger, 163 version: d.config.Version, 164 doneCh: make(chan struct{}), 165 waitCh: make(chan *dstructs.WaitResult, 1), 166 } 167 if err := exec.SyncServices(consulContext(d.config, "")); err != nil { 168 d.logger.Printf("[ERR] driver.exec: error registering services with consul for task: %q: %v", task.Name, err) 169 } 170 go h.run() 171 return 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 } 226 if err := exec.SyncServices(consulContext(d.config, "")); err != nil { 227 d.logger.Printf("[ERR] driver.exec: error registering services with consul: %v", err) 228 } 229 go h.run() 230 return h, nil 231 } 232 233 func (h *execHandle) ID() string { 234 id := execId{ 235 Version: h.version, 236 KillTimeout: h.killTimeout, 237 MaxKillTimeout: h.maxKillTimeout, 238 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 239 UserPid: h.userPid, 240 IsolationConfig: h.isolationConfig, 241 } 242 243 data, err := json.Marshal(id) 244 if err != nil { 245 h.logger.Printf("[ERR] driver.exec: failed to marshal ID to JSON: %s", err) 246 } 247 return string(data) 248 } 249 250 func (h *execHandle) WaitCh() chan *dstructs.WaitResult { 251 return h.waitCh 252 } 253 254 func (h *execHandle) Update(task *structs.Task) error { 255 // Store the updated kill timeout. 256 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 257 h.executor.UpdateTask(task) 258 259 // Update is not possible 260 return nil 261 } 262 263 func (h *execHandle) Signal(s os.Signal) error { 264 return h.executor.Signal(s) 265 } 266 267 func (h *execHandle) Kill() error { 268 if err := h.executor.ShutDown(); err != nil { 269 if h.pluginClient.Exited() { 270 return nil 271 } 272 return fmt.Errorf("executor Shutdown failed: %v", err) 273 } 274 275 select { 276 case <-h.doneCh: 277 case <-time.After(h.killTimeout): 278 if h.pluginClient.Exited() { 279 break 280 } 281 if err := h.executor.Exit(); err != nil { 282 return fmt.Errorf("executor Exit failed: %v", err) 283 } 284 } 285 return nil 286 } 287 288 func (h *execHandle) Stats() (*cstructs.TaskResourceUsage, error) { 289 return h.executor.Stats() 290 } 291 292 func (h *execHandle) run() { 293 ps, werr := h.executor.Wait() 294 close(h.doneCh) 295 296 // If the exitcode is 0 and we had an error that means the plugin didn't 297 // connect and doesn't know the state of the user process so we are killing 298 // the user process so that when we create a new executor on restarting the 299 // new user process doesn't have collisions with resources that the older 300 // user pid might be holding onto. 301 if ps.ExitCode == 0 && werr != nil { 302 if h.isolationConfig != nil { 303 ePid := h.pluginClient.ReattachConfig().Pid 304 if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil { 305 h.logger.Printf("[ERR] driver.exec: destroying resource container failed: %v", e) 306 } 307 } 308 } 309 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 // Exit the executor 316 if err := h.executor.Exit(); err != nil { 317 h.logger.Printf("[ERR] driver.exec: error destroying executor: %v", err) 318 } 319 h.pluginClient.Kill() 320 321 // Send the results 322 h.waitCh <- dstructs.NewWaitResult(ps.ExitCode, ps.Signal, werr) 323 close(h.waitCh) 324 }