github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/java.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "log" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/hashicorp/go-multierror" 17 "github.com/hashicorp/go-plugin" 18 "github.com/mitchellh/mapstructure" 19 20 "github.com/hashicorp/nomad/client/allocdir" 21 "github.com/hashicorp/nomad/client/config" 22 "github.com/hashicorp/nomad/client/driver/executor" 23 dstructs "github.com/hashicorp/nomad/client/driver/structs" 24 "github.com/hashicorp/nomad/client/fingerprint" 25 cstructs "github.com/hashicorp/nomad/client/structs" 26 "github.com/hashicorp/nomad/helper/discover" 27 "github.com/hashicorp/nomad/helper/fields" 28 "github.com/hashicorp/nomad/nomad/structs" 29 ) 30 31 const ( 32 // The key populated in Node Attributes to indicate presence of the Java 33 // driver 34 javaDriverAttr = "driver.java" 35 ) 36 37 // JavaDriver is a simple driver to execute applications packaged in Jars. 38 // It literally just fork/execs tasks with the java command. 39 type JavaDriver struct { 40 DriverContext 41 fingerprint.StaticFingerprinter 42 } 43 44 type JavaDriverConfig struct { 45 JarPath string `mapstructure:"jar_path"` 46 JvmOpts []string `mapstructure:"jvm_options"` 47 Args []string `mapstructure:"args"` 48 } 49 50 // javaHandle is returned from Start/Open as a handle to the PID 51 type javaHandle struct { 52 pluginClient *plugin.Client 53 userPid int 54 executor executor.Executor 55 isolationConfig *dstructs.IsolationConfig 56 57 taskDir string 58 allocDir *allocdir.AllocDir 59 killTimeout time.Duration 60 maxKillTimeout time.Duration 61 version string 62 logger *log.Logger 63 waitCh chan *dstructs.WaitResult 64 doneCh chan struct{} 65 } 66 67 // NewJavaDriver is used to create a new exec driver 68 func NewJavaDriver(ctx *DriverContext) Driver { 69 return &JavaDriver{DriverContext: *ctx} 70 } 71 72 // Validate is used to validate the driver configuration 73 func (d *JavaDriver) Validate(config map[string]interface{}) error { 74 fd := &fields.FieldData{ 75 Raw: config, 76 Schema: map[string]*fields.FieldSchema{ 77 "jar_path": &fields.FieldSchema{ 78 Type: fields.TypeString, 79 Required: true, 80 }, 81 "jvm_options": &fields.FieldSchema{ 82 Type: fields.TypeArray, 83 }, 84 "args": &fields.FieldSchema{ 85 Type: fields.TypeArray, 86 }, 87 }, 88 } 89 90 if err := fd.Validate(); err != nil { 91 return err 92 } 93 94 return nil 95 } 96 97 func (d *JavaDriver) Abilities() DriverAbilities { 98 return DriverAbilities{ 99 SendSignals: true, 100 } 101 } 102 103 func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 104 // Get the current status so that we can log any debug messages only if the 105 // state changes 106 _, currentlyEnabled := node.Attributes[javaDriverAttr] 107 108 // Only enable if we are root and cgroups are mounted when running on linux systems. 109 if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !d.cgroupsMounted(node)) { 110 if currentlyEnabled { 111 d.logger.Printf("[DEBUG] driver.java: root priviledges and mounted cgroups required on linux, disabling") 112 } 113 delete(node.Attributes, "driver.java") 114 return false, nil 115 } 116 117 // Find java version 118 var out bytes.Buffer 119 var erOut bytes.Buffer 120 cmd := exec.Command("java", "-version") 121 cmd.Stdout = &out 122 cmd.Stderr = &erOut 123 err := cmd.Run() 124 if err != nil { 125 // assume Java wasn't found 126 delete(node.Attributes, javaDriverAttr) 127 return false, nil 128 } 129 130 // 'java -version' returns output on Stderr typically. 131 // Check stdout, but it's probably empty 132 var infoString string 133 if out.String() != "" { 134 infoString = out.String() 135 } 136 137 if erOut.String() != "" { 138 infoString = erOut.String() 139 } 140 141 if infoString == "" { 142 if currentlyEnabled { 143 d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting") 144 } 145 delete(node.Attributes, javaDriverAttr) 146 return false, nil 147 } 148 149 // Assume 'java -version' returns 3 lines: 150 // java version "1.6.0_36" 151 // OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04) 152 // OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode) 153 // Each line is terminated by \n 154 info := strings.Split(infoString, "\n") 155 versionString := info[0] 156 versionString = strings.TrimPrefix(versionString, "java version ") 157 versionString = strings.Trim(versionString, "\"") 158 node.Attributes[javaDriverAttr] = "1" 159 node.Attributes["driver.java.version"] = versionString 160 node.Attributes["driver.java.runtime"] = info[1] 161 node.Attributes["driver.java.vm"] = info[2] 162 163 return true, nil 164 } 165 166 func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 167 var driverConfig JavaDriverConfig 168 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 169 return nil, err 170 } 171 172 // Set the host environment variables. 173 filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",") 174 d.taskEnv.AppendHostEnvvars(filter) 175 176 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 177 if !ok { 178 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 179 } 180 181 if driverConfig.JarPath == "" { 182 return nil, fmt.Errorf("jar_path must be specified") 183 } 184 185 args := []string{} 186 // Look for jvm options 187 if len(driverConfig.JvmOpts) != 0 { 188 d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts) 189 args = append(args, driverConfig.JvmOpts...) 190 } 191 192 // Build the argument list. 193 args = append(args, "-jar", driverConfig.JarPath) 194 if len(driverConfig.Args) != 0 { 195 args = append(args, driverConfig.Args...) 196 } 197 198 bin, err := discover.NomadExecutable() 199 if err != nil { 200 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 201 } 202 203 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 204 pluginConfig := &plugin.ClientConfig{ 205 Cmd: exec.Command(bin, "executor", pluginLogFile), 206 } 207 208 execIntf, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 209 if err != nil { 210 return nil, err 211 } 212 213 // Set the context 214 executorCtx := &executor.ExecutorContext{ 215 TaskEnv: d.taskEnv, 216 Driver: "java", 217 AllocDir: ctx.AllocDir, 218 AllocID: ctx.AllocID, 219 ChrootEnv: d.config.ChrootEnv, 220 Task: task, 221 } 222 if err := execIntf.SetContext(executorCtx); err != nil { 223 pluginClient.Kill() 224 return nil, fmt.Errorf("failed to set executor context: %v", err) 225 } 226 227 absPath, err := GetAbsolutePath("java") 228 if err != nil { 229 return nil, err 230 } 231 232 execCmd := &executor.ExecCommand{ 233 Cmd: absPath, 234 Args: args, 235 FSIsolation: true, 236 ResourceLimits: true, 237 User: getExecutorUser(task), 238 } 239 ps, err := execIntf.LaunchCmd(execCmd) 240 if err != nil { 241 pluginClient.Kill() 242 return nil, err 243 } 244 d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid) 245 246 // Return a driver handle 247 maxKill := d.DriverContext.config.MaxKillTimeout 248 h := &javaHandle{ 249 pluginClient: pluginClient, 250 executor: execIntf, 251 userPid: ps.Pid, 252 isolationConfig: ps.IsolationConfig, 253 taskDir: taskDir, 254 allocDir: ctx.AllocDir, 255 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 256 maxKillTimeout: maxKill, 257 version: d.config.Version, 258 logger: d.logger, 259 doneCh: make(chan struct{}), 260 waitCh: make(chan *dstructs.WaitResult, 1), 261 } 262 if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { 263 d.logger.Printf("[ERR] driver.java: error registering services with consul for task: %q: %v", task.Name, err) 264 } 265 go h.run() 266 return h, nil 267 } 268 269 // cgroupsMounted returns true if the cgroups are mounted on a system otherwise 270 // returns false 271 func (d *JavaDriver) cgroupsMounted(node *structs.Node) bool { 272 _, ok := node.Attributes["unique.cgroup.mountpoint"] 273 return ok 274 } 275 276 type javaId struct { 277 Version string 278 KillTimeout time.Duration 279 MaxKillTimeout time.Duration 280 PluginConfig *PluginReattachConfig 281 IsolationConfig *dstructs.IsolationConfig 282 TaskDir string 283 AllocDir *allocdir.AllocDir 284 UserPid int 285 } 286 287 func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 288 id := &javaId{} 289 if err := json.Unmarshal([]byte(handleID), id); err != nil { 290 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 291 } 292 293 pluginConfig := &plugin.ClientConfig{ 294 Reattach: id.PluginConfig.PluginConfig(), 295 } 296 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 297 if err != nil { 298 merrs := new(multierror.Error) 299 merrs.Errors = append(merrs.Errors, err) 300 d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid") 301 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 302 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 303 } 304 if id.IsolationConfig != nil { 305 ePid := pluginConfig.Reattach.Pid 306 if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil { 307 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying resource container failed: %v", e)) 308 } 309 } 310 if e := ctx.AllocDir.UnmountAll(); e != nil { 311 merrs.Errors = append(merrs.Errors, e) 312 } 313 314 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 315 } 316 317 ver, _ := exec.Version() 318 d.logger.Printf("[DEBUG] driver.java: version of executor: %v", ver.Version) 319 320 // Return a driver handle 321 h := &javaHandle{ 322 pluginClient: pluginClient, 323 executor: exec, 324 userPid: id.UserPid, 325 isolationConfig: id.IsolationConfig, 326 taskDir: id.TaskDir, 327 allocDir: id.AllocDir, 328 logger: d.logger, 329 version: id.Version, 330 killTimeout: id.KillTimeout, 331 maxKillTimeout: id.MaxKillTimeout, 332 doneCh: make(chan struct{}), 333 waitCh: make(chan *dstructs.WaitResult, 1), 334 } 335 if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { 336 d.logger.Printf("[ERR] driver.java: error registering services with consul: %v", err) 337 } 338 339 go h.run() 340 return h, nil 341 } 342 343 func (h *javaHandle) ID() string { 344 id := javaId{ 345 Version: h.version, 346 KillTimeout: h.killTimeout, 347 MaxKillTimeout: h.maxKillTimeout, 348 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 349 UserPid: h.userPid, 350 TaskDir: h.taskDir, 351 AllocDir: h.allocDir, 352 IsolationConfig: h.isolationConfig, 353 } 354 355 data, err := json.Marshal(id) 356 if err != nil { 357 h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err) 358 } 359 return string(data) 360 } 361 362 func (h *javaHandle) WaitCh() chan *dstructs.WaitResult { 363 return h.waitCh 364 } 365 366 func (h *javaHandle) Update(task *structs.Task) error { 367 // Store the updated kill timeout. 368 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 369 h.executor.UpdateTask(task) 370 371 // Update is not possible 372 return nil 373 } 374 375 func (h *javaHandle) Signal(s os.Signal) error { 376 return h.executor.Signal(s) 377 } 378 379 func (h *javaHandle) Kill() error { 380 if err := h.executor.ShutDown(); err != nil { 381 if h.pluginClient.Exited() { 382 return nil 383 } 384 return fmt.Errorf("executor Shutdown failed: %v", err) 385 } 386 387 select { 388 case <-h.doneCh: 389 return nil 390 case <-time.After(h.killTimeout): 391 if h.pluginClient.Exited() { 392 return nil 393 } 394 if err := h.executor.Exit(); err != nil { 395 return fmt.Errorf("executor Exit failed: %v", err) 396 } 397 398 return nil 399 } 400 } 401 402 func (h *javaHandle) Stats() (*cstructs.TaskResourceUsage, error) { 403 return h.executor.Stats() 404 } 405 406 func (h *javaHandle) run() { 407 ps, werr := h.executor.Wait() 408 close(h.doneCh) 409 if ps.ExitCode == 0 && werr != nil { 410 if h.isolationConfig != nil { 411 ePid := h.pluginClient.ReattachConfig().Pid 412 if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil { 413 h.logger.Printf("[ERR] driver.java: destroying resource container failed: %v", e) 414 } 415 } else { 416 if e := killProcess(h.userPid); e != nil { 417 h.logger.Printf("[ERR] driver.java: error killing user process: %v", e) 418 } 419 } 420 if e := h.allocDir.UnmountAll(); e != nil { 421 h.logger.Printf("[ERR] driver.java: unmounting dev,proc and alloc dirs failed: %v", e) 422 } 423 } 424 425 // Remove services 426 if err := h.executor.DeregisterServices(); err != nil { 427 h.logger.Printf("[ERR] driver.java: failed to kill the deregister services: %v", err) 428 } 429 430 // Exit the executor 431 h.executor.Exit() 432 h.pluginClient.Kill() 433 434 // Send the results 435 h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: werr} 436 close(h.waitCh) 437 }