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