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