github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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/config" 22 "github.com/hashicorp/nomad/client/driver/env" 23 "github.com/hashicorp/nomad/client/driver/executor" 24 dstructs "github.com/hashicorp/nomad/client/driver/structs" 25 "github.com/hashicorp/nomad/client/fingerprint" 26 cstructs "github.com/hashicorp/nomad/client/structs" 27 "github.com/hashicorp/nomad/helper" 28 "github.com/hashicorp/nomad/helper/fields" 29 "github.com/hashicorp/nomad/nomad/structs" 30 ) 31 32 const ( 33 // The key populated in Node Attributes to indicate presence of the Java 34 // driver 35 javaDriverAttr = "driver.java" 36 ) 37 38 // JavaDriver is a simple driver to execute applications packaged in Jars. 39 // It literally just fork/execs tasks with the java command. 40 type JavaDriver struct { 41 DriverContext 42 fingerprint.StaticFingerprinter 43 44 // A tri-state boolean to know if the fingerprinting has happened and 45 // whether it has been successful 46 fingerprintSuccess *bool 47 } 48 49 type JavaDriverConfig struct { 50 Class string `mapstructure:"class"` 51 ClassPath string `mapstructure:"class_path"` 52 JarPath string `mapstructure:"jar_path"` 53 JvmOpts []string `mapstructure:"jvm_options"` 54 Args []string `mapstructure:"args"` 55 } 56 57 // javaHandle is returned from Start/Open as a handle to the PID 58 type javaHandle struct { 59 pluginClient *plugin.Client 60 userPid int 61 executor executor.Executor 62 isolationConfig *dstructs.IsolationConfig 63 taskDir string 64 65 killTimeout time.Duration 66 maxKillTimeout time.Duration 67 version string 68 logger *log.Logger 69 waitCh chan *dstructs.WaitResult 70 doneCh chan struct{} 71 } 72 73 // NewJavaDriver is used to create a new exec driver 74 func NewJavaDriver(ctx *DriverContext) Driver { 75 return &JavaDriver{DriverContext: *ctx} 76 } 77 78 // Validate is used to validate the driver configuration 79 func (d *JavaDriver) Validate(config map[string]interface{}) error { 80 fd := &fields.FieldData{ 81 Raw: config, 82 Schema: map[string]*fields.FieldSchema{ 83 "class": &fields.FieldSchema{ 84 Type: fields.TypeString, 85 }, 86 "class_path": &fields.FieldSchema{ 87 Type: fields.TypeString, 88 }, 89 "jar_path": &fields.FieldSchema{ 90 Type: fields.TypeString, 91 }, 92 "jvm_options": &fields.FieldSchema{ 93 Type: fields.TypeArray, 94 }, 95 "args": &fields.FieldSchema{ 96 Type: fields.TypeArray, 97 }, 98 }, 99 } 100 101 if err := fd.Validate(); err != nil { 102 return err 103 } 104 105 return nil 106 } 107 108 func (d *JavaDriver) Abilities() DriverAbilities { 109 return DriverAbilities{ 110 SendSignals: true, 111 Exec: true, 112 } 113 } 114 115 func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 116 // Only enable if we are root and cgroups are mounted when running on linux systems. 117 if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !cgroupsMounted(node)) { 118 if d.fingerprintSuccess == nil || *d.fingerprintSuccess { 119 d.logger.Printf("[DEBUG] driver.java: root priviledges and mounted cgroups required on linux, disabling") 120 } 121 delete(node.Attributes, "driver.java") 122 d.fingerprintSuccess = helper.BoolToPtr(false) 123 return false, 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 delete(node.Attributes, javaDriverAttr) 136 d.fingerprintSuccess = helper.BoolToPtr(false) 137 return false, 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 delete(node.Attributes, javaDriverAttr) 156 d.fingerprintSuccess = helper.BoolToPtr(false) 157 return false, 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 node.Attributes[javaDriverAttr] = "1" 170 node.Attributes["driver.java.version"] = versionString 171 node.Attributes["driver.java.runtime"] = info[1] 172 node.Attributes["driver.java.vm"] = info[2] 173 d.fingerprintSuccess = helper.BoolToPtr(true) 174 175 return true, nil 176 } 177 178 func (d *JavaDriver) Prestart(*ExecContext, *structs.Task) (*PrestartResponse, error) { 179 return nil, nil 180 } 181 182 func NewJavaDriverConfig(task *structs.Task, env *env.TaskEnv) (*JavaDriverConfig, error) { 183 var driverConfig JavaDriverConfig 184 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 185 return nil, err 186 } 187 188 // Interpolate everything 189 driverConfig.Class = env.ReplaceEnv(driverConfig.Class) 190 driverConfig.ClassPath = env.ReplaceEnv(driverConfig.ClassPath) 191 driverConfig.JarPath = env.ReplaceEnv(driverConfig.JarPath) 192 driverConfig.JvmOpts = env.ParseAndReplace(driverConfig.JvmOpts) 193 driverConfig.Args = env.ParseAndReplace(driverConfig.Args) 194 195 // Validate 196 jarSpecified := driverConfig.JarPath != "" 197 classSpecified := driverConfig.Class != "" 198 if !jarSpecified && !classSpecified { 199 return nil, fmt.Errorf("jar_path or class must be specified") 200 } 201 202 return &driverConfig, nil 203 } 204 205 func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) { 206 driverConfig, err := NewJavaDriverConfig(task, ctx.TaskEnv) 207 if err != nil { 208 return nil, err 209 } 210 211 args := []string{} 212 213 // Look for jvm options 214 if len(driverConfig.JvmOpts) != 0 { 215 d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts) 216 args = append(args, driverConfig.JvmOpts...) 217 } 218 219 // Add the classpath 220 if driverConfig.ClassPath != "" { 221 args = append(args, "-cp", driverConfig.ClassPath) 222 } 223 224 // Add the jar 225 if driverConfig.JarPath != "" { 226 args = append(args, "-jar", driverConfig.JarPath) 227 } 228 229 // Add the class 230 if driverConfig.Class != "" { 231 args = append(args, driverConfig.Class) 232 } 233 234 // Add any args 235 if len(driverConfig.Args) != 0 { 236 args = append(args, driverConfig.Args...) 237 } 238 239 pluginLogFile := filepath.Join(ctx.TaskDir.Dir, "executor.out") 240 executorConfig := &dstructs.ExecutorConfig{ 241 LogFile: pluginLogFile, 242 LogLevel: d.config.LogLevel, 243 } 244 245 execIntf, pluginClient, err := createExecutor(d.config.LogOutput, d.config, executorConfig) 246 if err != nil { 247 return nil, err 248 } 249 250 // Set the context 251 executorCtx := &executor.ExecutorContext{ 252 TaskEnv: ctx.TaskEnv, 253 Driver: "java", 254 AllocID: d.DriverContext.allocID, 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 execCmd := &executor.ExecCommand{ 270 Cmd: absPath, 271 Args: args, 272 FSIsolation: true, 273 ResourceLimits: true, 274 User: getExecutorUser(task), 275 } 276 ps, err := execIntf.LaunchCmd(execCmd) 277 if err != nil { 278 pluginClient.Kill() 279 return nil, err 280 } 281 d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid) 282 283 // Return a driver handle 284 maxKill := d.DriverContext.config.MaxKillTimeout 285 h := &javaHandle{ 286 pluginClient: pluginClient, 287 executor: execIntf, 288 userPid: ps.Pid, 289 isolationConfig: ps.IsolationConfig, 290 taskDir: ctx.TaskDir.Dir, 291 killTimeout: GetKillTimeout(task.KillTimeout, maxKill), 292 maxKillTimeout: maxKill, 293 version: d.config.Version, 294 logger: d.logger, 295 doneCh: make(chan struct{}), 296 waitCh: make(chan *dstructs.WaitResult, 1), 297 } 298 go h.run() 299 return &StartResponse{Handle: h}, nil 300 } 301 302 func (d *JavaDriver) Cleanup(*ExecContext, *CreatedResources) error { return nil } 303 304 type javaId struct { 305 Version string 306 KillTimeout time.Duration 307 MaxKillTimeout time.Duration 308 PluginConfig *PluginReattachConfig 309 IsolationConfig *dstructs.IsolationConfig 310 TaskDir string 311 UserPid int 312 } 313 314 func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 315 id := &javaId{} 316 if err := json.Unmarshal([]byte(handleID), id); err != nil { 317 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 318 } 319 320 pluginConfig := &plugin.ClientConfig{ 321 Reattach: id.PluginConfig.PluginConfig(), 322 } 323 exec, pluginClient, err := createExecutorWithConfig(pluginConfig, d.config.LogOutput) 324 if err != nil { 325 merrs := new(multierror.Error) 326 merrs.Errors = append(merrs.Errors, err) 327 d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid") 328 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 329 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 330 } 331 if id.IsolationConfig != nil { 332 ePid := pluginConfig.Reattach.Pid 333 if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil { 334 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying resource container failed: %v", e)) 335 } 336 } 337 338 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 339 } 340 341 ver, _ := exec.Version() 342 d.logger.Printf("[DEBUG] driver.java: version of executor: %v", ver.Version) 343 344 // Return a driver handle 345 h := &javaHandle{ 346 pluginClient: pluginClient, 347 executor: exec, 348 userPid: id.UserPid, 349 isolationConfig: id.IsolationConfig, 350 logger: d.logger, 351 version: id.Version, 352 killTimeout: id.KillTimeout, 353 maxKillTimeout: id.MaxKillTimeout, 354 doneCh: make(chan struct{}), 355 waitCh: make(chan *dstructs.WaitResult, 1), 356 } 357 go h.run() 358 return h, nil 359 } 360 361 func (h *javaHandle) ID() string { 362 id := javaId{ 363 Version: h.version, 364 KillTimeout: h.killTimeout, 365 MaxKillTimeout: h.maxKillTimeout, 366 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 367 UserPid: h.userPid, 368 IsolationConfig: h.isolationConfig, 369 TaskDir: h.taskDir, 370 } 371 372 data, err := json.Marshal(id) 373 if err != nil { 374 h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err) 375 } 376 return string(data) 377 } 378 379 func (h *javaHandle) WaitCh() chan *dstructs.WaitResult { 380 return h.waitCh 381 } 382 383 func (h *javaHandle) Update(task *structs.Task) error { 384 // Store the updated kill timeout. 385 h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout) 386 h.executor.UpdateTask(task) 387 388 // Update is not possible 389 return nil 390 } 391 392 func (h *javaHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { 393 deadline, ok := ctx.Deadline() 394 if !ok { 395 // No deadline set on context; default to 1 minute 396 deadline = time.Now().Add(time.Minute) 397 } 398 return h.executor.Exec(deadline, cmd, args) 399 } 400 401 func (h *javaHandle) Signal(s os.Signal) error { 402 return h.executor.Signal(s) 403 } 404 405 func (h *javaHandle) Kill() error { 406 if err := h.executor.ShutDown(); err != nil { 407 if h.pluginClient.Exited() { 408 return nil 409 } 410 return fmt.Errorf("executor Shutdown failed: %v", err) 411 } 412 413 select { 414 case <-h.doneCh: 415 case <-time.After(h.killTimeout): 416 if h.pluginClient.Exited() { 417 break 418 } 419 if err := h.executor.Exit(); err != nil { 420 return fmt.Errorf("executor Exit failed: %v", err) 421 } 422 423 } 424 return nil 425 } 426 427 func (h *javaHandle) Stats() (*cstructs.TaskResourceUsage, error) { 428 return h.executor.Stats() 429 } 430 431 func (h *javaHandle) run() { 432 ps, werr := h.executor.Wait() 433 close(h.doneCh) 434 if ps.ExitCode == 0 && werr != nil { 435 if h.isolationConfig != nil { 436 ePid := h.pluginClient.ReattachConfig().Pid 437 if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil { 438 h.logger.Printf("[ERR] driver.java: destroying resource container failed: %v", e) 439 } 440 } else { 441 if e := killProcess(h.userPid); e != nil { 442 h.logger.Printf("[ERR] driver.java: error killing user process: %v", e) 443 } 444 } 445 } 446 447 // Exit the executor 448 h.executor.Exit() 449 h.pluginClient.Kill() 450 451 // Send the results 452 h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: werr} 453 close(h.waitCh) 454 }