github.com/anuvu/nomad@v0.8.7-atom1/client/driver/java.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "log" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/hashicorp/go-multierror" 18 "github.com/hashicorp/go-plugin" 19 "github.com/mitchellh/mapstructure" 20 21 "github.com/hashicorp/nomad/client/driver/env" 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" 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 // A tri-state boolean to know if the fingerprinting has happened and 44 // whether it has been successful 45 fingerprintSuccess *bool 46 } 47 48 type JavaDriverConfig struct { 49 Class string `mapstructure:"class"` 50 ClassPath string `mapstructure:"class_path"` 51 JarPath string `mapstructure:"jar_path"` 52 JvmOpts []string `mapstructure:"jvm_options"` 53 Args []string `mapstructure:"args"` 54 } 55 56 // javaHandle is returned from Start/Open as a handle to the PID 57 type javaHandle struct { 58 pluginClient *plugin.Client 59 userPid int 60 executor executor.Executor 61 isolationConfig *dstructs.IsolationConfig 62 taskDir string 63 64 killTimeout time.Duration 65 maxKillTimeout time.Duration 66 version string 67 logger *log.Logger 68 waitCh chan *dstructs.WaitResult 69 doneCh chan struct{} 70 } 71 72 // NewJavaDriver is used to create a new exec driver 73 func NewJavaDriver(ctx *DriverContext) Driver { 74 return &JavaDriver{DriverContext: *ctx} 75 } 76 77 // Validate is used to validate the driver configuration 78 func (d *JavaDriver) Validate(config map[string]interface{}) error { 79 fd := &fields.FieldData{ 80 Raw: config, 81 Schema: map[string]*fields.FieldSchema{ 82 "class": { 83 Type: fields.TypeString, 84 }, 85 "class_path": { 86 Type: fields.TypeString, 87 }, 88 "jar_path": { 89 Type: fields.TypeString, 90 }, 91 "jvm_options": { 92 Type: fields.TypeArray, 93 }, 94 "args": { 95 Type: fields.TypeArray, 96 }, 97 }, 98 } 99 100 if err := fd.Validate(); err != nil { 101 return err 102 } 103 104 return nil 105 } 106 107 func (d *JavaDriver) Abilities() DriverAbilities { 108 return DriverAbilities{ 109 SendSignals: true, 110 Exec: true, 111 } 112 } 113 114 func (d *JavaDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 115 // Only enable if we are root and cgroups are mounted when running on linux systems. 116 if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !cgroupsMounted(req.Node)) { 117 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 118 d.logger.Printf("[INFO] driver.java: root privileges and mounted cgroups required on linux, disabling") 119 } 120 d.fingerprintSuccess = helper.BoolToPtr(false) 121 resp.RemoveAttribute(javaDriverAttr) 122 resp.Detected = true 123 return nil 124 } 125 126 // Find java version 127 var out bytes.Buffer 128 var erOut bytes.Buffer 129 cmd := exec.Command("java", "-version") 130 cmd.Stdout = &out 131 cmd.Stderr = &erOut 132 err := cmd.Run() 133 if err != nil { 134 // assume Java wasn't found 135 d.fingerprintSuccess = helper.BoolToPtr(false) 136 resp.RemoveAttribute(javaDriverAttr) 137 return nil 138 } 139 140 // 'java -version' returns output on Stderr typically. 141 // Check stdout, but it's probably empty 142 var infoString string 143 if out.String() != "" { 144 infoString = out.String() 145 } 146 147 if erOut.String() != "" { 148 infoString = erOut.String() 149 } 150 151 if infoString == "" { 152 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 153 d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting") 154 } 155 d.fingerprintSuccess = helper.BoolToPtr(false) 156 resp.RemoveAttribute(javaDriverAttr) 157 return nil 158 } 159 160 // Assume 'java -version' returns 3 lines: 161 // java version "1.6.0_36" 162 // OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04) 163 // OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode) 164 // Each line is terminated by \n 165 info := strings.Split(infoString, "\n") 166 versionString := info[0] 167 versionString = strings.TrimPrefix(versionString, "java version ") 168 versionString = strings.Trim(versionString, "\"") 169 resp.AddAttribute(javaDriverAttr, "1") 170 resp.AddAttribute("driver.java.version", versionString) 171 resp.AddAttribute("driver.java.runtime", info[1]) 172 resp.AddAttribute("driver.java.vm", info[2]) 173 d.fingerprintSuccess = helper.BoolToPtr(true) 174 resp.Detected = true 175 176 return nil 177 } 178 179 func (d *JavaDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) { 180 return nil, nil 181 } 182 183 func NewJavaDriverConfig(task *structs.Task, env *env.TaskEnv) (*JavaDriverConfig, error) { 184 var driverConfig JavaDriverConfig 185 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 186 return nil, err 187 } 188 189 // Interpolate everything 190 driverConfig.Class = env.ReplaceEnv(driverConfig.Class) 191 driverConfig.ClassPath = env.ReplaceEnv(driverConfig.ClassPath) 192 driverConfig.JarPath = env.ReplaceEnv(driverConfig.JarPath) 193 driverConfig.JvmOpts = env.ParseAndReplace(driverConfig.JvmOpts) 194 driverConfig.Args = env.ParseAndReplace(driverConfig.Args) 195 196 // Validate 197 jarSpecified := driverConfig.JarPath != "" 198 classSpecified := driverConfig.Class != "" 199 if !jarSpecified && !classSpecified { 200 return nil, fmt.Errorf("jar_path or class must be specified") 201 } 202 203 return &driverConfig, nil 204 } 205 206 func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 207 driverConfig, err := NewJavaDriverConfig(task, ctx.TaskEnv) 208 if err != nil { 209 return nil, err 210 } 211 212 args := []string{} 213 214 // Look for jvm options 215 if len(driverConfig.JvmOpts) != 0 { 216 d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts) 217 args = append(args, driverConfig.JvmOpts...) 218 } 219 220 // Add the classpath 221 if driverConfig.ClassPath != "" { 222 args = append(args, "-cp", driverConfig.ClassPath) 223 } 224 225 // Add the jar 226 if driverConfig.JarPath != "" { 227 args = append(args, "-jar", driverConfig.JarPath) 228 } 229 230 // Add the class 231 if driverConfig.Class != "" { 232 args = append(args, driverConfig.Class) 233 } 234 235 // Add any args 236 if len(driverConfig.Args) != 0 { 237 args = append(args, driverConfig.Args...) 238 } 239 240 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out") 241 executorConfig := &dstructs.ExecutorConfig{ 242 LogFile: pluginLogFile, 243 LogLevel: d.config.LogLevel, 244 } 245 246 execIntf, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 247 if err != nil { 248 return nil, err 249 } 250 251 // Set the context 252 executorCtx := &executor.ExecutorContext{ 253 TaskEnv: ctx.TaskEnv, 254 Driver: "java", 255 Task: task, 256 TaskDir: ctx.TaskDir.Dir, 257 LogDir: ctx.TaskDir.LogDir, 258 } 259 if err := execIntf.SetContext(executorCtx); err != nil { 260 pluginClient.Kill() 261 return nil, fmt.Errorf("failed to set executor context: %v", err) 262 } 263 264 absPath, err := GetAbsolutePath("java") 265 if err != nil { 266 return nil, err 267 } 268 269 taskKillSignal, err := getTaskKillSignal(task.KillSignal) 270 if err != nil { 271 return nil, err 272 } 273 274 execCmd := &executor.ExecCommand{ 275 Cmd: absPath, 276 Args: args, 277 FSIsolation: true, 278 ResourceLimits: true, 279 User: getExecutorUser(task), 280 TaskKillSignal: taskKillSignal, 281 } 282 ps, err := execIntf.LaunchCmd(execCmd) 283 if err != nil { 284 pluginClient.Kill() 285 return nil, err 286 } 287 d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid) 288 289 // Return a driver handle 290 maxKill := d.DriverContext.config.MaxKillTimeout 291 h := &javaHandle{ 292 pluginClient: pluginClient, 293 executor: execIntf, 294 userPid: ps.Pid, 295 isolationConfig: ps.IsolationConfig, 296 taskDir: ctx.TaskDir.Dir, 297 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 298 maxKillTimeout: maxKill, 299 version: d.config.Version.VersionNumber(), 300 logger: d.logger, 301 doneCh: make(chan struct{}), 302 waitCh: make(chan *dstructs.WaitResult, 1), 303 } 304 go h.run() 305 return &StartResponse{Handle: h}, nil 306 } 307 308 func (d *JavaDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 309 310 type javaId struct { 311 Version string 312 KillTimeout time.Duration 313 MaxKillTimeout time.Duration 314 PluginConfig *PluginReattachConfig 315 IsolationConfig *dstructs.IsolationConfig 316 TaskDir string 317 UserPid int 318 } 319 320 func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 321 id := &javaId{} 322 if err := json.Unmarshal([]byte(handleID), id); err != nil { 323 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 324 } 325 326 pluginConfig := &plugin.ClientConfig{ 327 Reattach: id.PluginConfig.PluginConfig(), 328 } 329 exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 330 if err != nil { 331 merrs := new(multierror.Error) 332 merrs.Errors = append(merrs.Errors, err) 333 d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid") 334 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 335 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 336 } 337 if id.IsolationConfig != nil { 338 ePid := pluginConfig.Reattach.Pid 339 if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil { 340 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying resource container failed: %v", e)) 341 } 342 } 343 344 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 345 } 346 347 ver, _ := exec.Version() 348 d.logger.Printf("[DEBUG] driver.java: version of executor: %v", ver.Version) 349 350 // Return a driver handle 351 h := &javaHandle{ 352 pluginClient: pluginClient, 353 executor: exec, 354 userPid: id.UserPid, 355 isolationConfig: id.IsolationConfig, 356 logger: d.logger, 357 version: id.Version, 358 killTimeout: id.KillTimeout, 359 maxKillTimeout: id.MaxKillTimeout, 360 doneCh: make(chan struct{}), 361 waitCh: make(chan *dstructs.WaitResult, 1), 362 } 363 go h.run() 364 return h, nil 365 } 366 367 func (h *javaHandle) ID() string { 368 id := javaId{ 369 Version: h.version, 370 KillTimeout: h.killTimeout, 371 MaxKillTimeout: h.maxKillTimeout, 372 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 373 UserPid: h.userPid, 374 IsolationConfig: h.isolationConfig, 375 TaskDir: h.taskDir, 376 } 377 378 data, err := json.Marshal(id) 379 if err != nil { 380 h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err) 381 } 382 return string(data) 383 } 384 385 func (h *javaHandle) WaitCh() chan *dstructs.WaitResult { 386 return h.waitCh 387 } 388 389 func (h *javaHandle) Update(task *structs.Task) error { 390 // Store the updated kill timeout. 391 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 392 h.executor.UpdateTask(task) 393 394 // Update is not possible 395 return nil 396 } 397 398 func (h *javaHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 399 deadline, ok := ctx.Deadline() 400 if !ok { 401 // No deadline set on context; default to 1 minute 402 deadline = time.Now().Add(time.Minute) 403 } 404 return h.executor.Exec(deadline, cmd, args) 405 } 406 407 func (h *javaHandle) Signal(s os.Signal) error { 408 return h.executor.Signal(s) 409 } 410 411 func (h *javaHandle) Kill() error { 412 if err := h.executor.ShutDown(); err != nil { 413 if h.pluginClient.Exited() { 414 return nil 415 } 416 return fmt.Errorf("executor Shutdown failed: %v", err) 417 } 418 419 select { 420 case <-h.doneCh: 421 case <-time.After(h.killTimeout): 422 if h.pluginClient.Exited() { 423 break 424 } 425 if err := h.executor.Exit(); err != nil { 426 return fmt.Errorf("executor Exit failed: %v", err) 427 } 428 429 } 430 return nil 431 } 432 433 func (h *javaHandle) Stats() (*cstructs.TaskResourceUsage, error) { 434 return h.executor.Stats() 435 } 436 437 func (h *javaHandle) run() { 438 ps, werr := h.executor.Wait() 439 close(h.doneCh) 440 if ps.ExitCode == 0 && werr != nil { 441 if h.isolationConfig != nil { 442 ePid := h.pluginClient.ReattachConfig().Pid 443 if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil { 444 h.logger.Printf("[ERR] driver.java: destroying resource container failed: %v", e) 445 } 446 } else { 447 if e := killProcess(h.userPid); e != nil { 448 h.logger.Printf("[ERR] driver.java: error killing user process: %v", e) 449 } 450 } 451 } 452 453 // Exit the executor 454 h.executor.Exit() 455 h.pluginClient.Kill() 456 457 // Send the results 458 h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: werr} 459 close(h.waitCh) 460 }