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