github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/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/client/getter" 25 "github.com/hashicorp/nomad/helper/discover" 26 "github.com/hashicorp/nomad/nomad/structs" 27 ) 28 29 // JavaDriver is a simple driver to execute applications packaged in Jars. 30 // It literally just fork/execs tasks with the java command. 31 type JavaDriver struct { 32 DriverContext 33 fingerprint.StaticFingerprinter 34 } 35 36 type JavaDriverConfig struct { 37 JvmOpts []string `mapstructure:"jvm_options"` 38 ArtifactSource string `mapstructure:"artifact_source"` 39 Checksum string `mapstructure:"checksum"` 40 Args []string `mapstructure:"args"` 41 } 42 43 // javaHandle is returned from Start/Open as a handle to the PID 44 type javaHandle struct { 45 pluginClient *plugin.Client 46 userPid int 47 executor executor.Executor 48 isolationConfig *cstructs.IsolationConfig 49 50 taskDir string 51 allocDir *allocdir.AllocDir 52 killTimeout time.Duration 53 version string 54 logger *log.Logger 55 waitCh chan *cstructs.WaitResult 56 doneCh chan struct{} 57 } 58 59 // NewJavaDriver is used to create a new exec driver 60 func NewJavaDriver(ctx *DriverContext) Driver { 61 return &JavaDriver{DriverContext: *ctx} 62 } 63 64 func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 65 // Only enable if we are root and cgroups are mounted when running on linux systems. 66 if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !d.cgroupsMounted(node)) { 67 d.logger.Printf("[DEBUG] driver.java: must run as root user on linux, disabling") 68 return false, nil 69 } 70 71 // Find java version 72 var out bytes.Buffer 73 var erOut bytes.Buffer 74 cmd := exec.Command("java", "-version") 75 cmd.Stdout = &out 76 cmd.Stderr = &erOut 77 err := cmd.Run() 78 if err != nil { 79 // assume Java wasn't found 80 return false, nil 81 } 82 83 // 'java -version' returns output on Stderr typically. 84 // Check stdout, but it's probably empty 85 var infoString string 86 if out.String() != "" { 87 infoString = out.String() 88 } 89 90 if erOut.String() != "" { 91 infoString = erOut.String() 92 } 93 94 if infoString == "" { 95 d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting") 96 return false, nil 97 } 98 99 // Assume 'java -version' returns 3 lines: 100 // java version "1.6.0_36" 101 // OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04) 102 // OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode) 103 // Each line is terminated by \n 104 info := strings.Split(infoString, "\n") 105 versionString := info[0] 106 versionString = strings.TrimPrefix(versionString, "java version ") 107 versionString = strings.Trim(versionString, "\"") 108 node.Attributes["driver.java"] = "1" 109 node.Attributes["driver.java.version"] = versionString 110 node.Attributes["driver.java.runtime"] = info[1] 111 node.Attributes["driver.java.vm"] = info[2] 112 113 return true, nil 114 } 115 116 func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 117 var driverConfig JavaDriverConfig 118 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 119 return nil, err 120 } 121 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 122 if !ok { 123 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 124 } 125 126 // Proceed to download an artifact to be executed. 127 path, err := getter.GetArtifact( 128 taskDir, 129 driverConfig.ArtifactSource, 130 driverConfig.Checksum, 131 d.logger, 132 ) 133 if err != nil { 134 return nil, err 135 } 136 137 jarName := filepath.Base(path) 138 139 args := []string{} 140 // Look for jvm options 141 if len(driverConfig.JvmOpts) != 0 { 142 d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts) 143 args = append(args, driverConfig.JvmOpts...) 144 } 145 146 // Build the argument list. 147 args = append(args, "-jar", jarName) 148 if len(driverConfig.Args) != 0 { 149 args = append(args, driverConfig.Args...) 150 } 151 152 bin, err := discover.NomadExecutable() 153 if err != nil { 154 return nil, fmt.Errorf("unable to find the nomad binary: %v", err) 155 } 156 157 pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name)) 158 pluginConfig := &plugin.ClientConfig{ 159 Cmd: exec.Command(bin, "executor", pluginLogFile), 160 } 161 162 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 163 if err != nil { 164 return nil, err 165 } 166 executorCtx := &executor.ExecutorContext{ 167 TaskEnv: d.taskEnv, 168 AllocDir: ctx.AllocDir, 169 TaskName: task.Name, 170 TaskResources: task.Resources, 171 LogConfig: task.LogConfig, 172 FSIsolation: true, 173 UnprivilegedUser: true, 174 ResourceLimits: true, 175 } 176 177 ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: "java", Args: args}, executorCtx) 178 if err != nil { 179 pluginClient.Kill() 180 return nil, fmt.Errorf("error starting process via the plugin: %v", err) 181 } 182 d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid) 183 184 // Return a driver handle 185 h := &javaHandle{ 186 pluginClient: pluginClient, 187 executor: exec, 188 userPid: ps.Pid, 189 isolationConfig: ps.IsolationConfig, 190 taskDir: taskDir, 191 allocDir: ctx.AllocDir, 192 killTimeout: d.DriverContext.KillTimeout(task), 193 version: d.config.Version, 194 logger: d.logger, 195 doneCh: make(chan struct{}), 196 waitCh: make(chan *cstructs.WaitResult, 1), 197 } 198 199 go h.run() 200 return h, nil 201 } 202 203 // cgroupsMounted returns true if the cgroups are mounted on a system otherwise 204 // returns false 205 func (d *JavaDriver) cgroupsMounted(node *structs.Node) bool { 206 _, ok := node.Attributes["unique.cgroup.mountpoint"] 207 return ok 208 } 209 210 type javaId struct { 211 Version string 212 KillTimeout time.Duration 213 PluginConfig *PluginReattachConfig 214 IsolationConfig *cstructs.IsolationConfig 215 TaskDir string 216 AllocDir *allocdir.AllocDir 217 UserPid int 218 } 219 220 func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 221 id := &javaId{} 222 if err := json.Unmarshal([]byte(handleID), id); err != nil { 223 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 224 } 225 226 pluginConfig := &plugin.ClientConfig{ 227 Reattach: id.PluginConfig.PluginConfig(), 228 } 229 exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config) 230 if err != nil { 231 merrs := new(multierror.Error) 232 merrs.Errors = append(merrs.Errors, err) 233 d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid") 234 if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil { 235 merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e)) 236 } 237 if id.IsolationConfig != nil { 238 if e := executor.DestroyCgroup(id.IsolationConfig.Cgroup); e != nil { 239 merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying cgroup failed: %v", e)) 240 } 241 } 242 if e := ctx.AllocDir.UnmountAll(); e != nil { 243 merrs.Errors = append(merrs.Errors, e) 244 } 245 246 return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil()) 247 } 248 249 // Return a driver handle 250 h := &javaHandle{ 251 pluginClient: pluginClient, 252 executor: exec, 253 userPid: id.UserPid, 254 isolationConfig: id.IsolationConfig, 255 taskDir: id.TaskDir, 256 allocDir: id.AllocDir, 257 logger: d.logger, 258 version: id.Version, 259 killTimeout: id.KillTimeout, 260 doneCh: make(chan struct{}), 261 waitCh: make(chan *cstructs.WaitResult, 1), 262 } 263 264 go h.run() 265 return h, nil 266 } 267 268 func (h *javaHandle) ID() string { 269 id := javaId{ 270 Version: h.version, 271 KillTimeout: h.killTimeout, 272 PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()), 273 UserPid: h.userPid, 274 TaskDir: h.taskDir, 275 AllocDir: h.allocDir, 276 IsolationConfig: h.isolationConfig, 277 } 278 279 data, err := json.Marshal(id) 280 if err != nil { 281 h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err) 282 } 283 return string(data) 284 } 285 286 func (h *javaHandle) WaitCh() chan *cstructs.WaitResult { 287 return h.waitCh 288 } 289 290 func (h *javaHandle) Update(task *structs.Task) error { 291 // Store the updated kill timeout. 292 h.killTimeout = task.KillTimeout 293 h.executor.UpdateLogConfig(task.LogConfig) 294 295 // Update is not possible 296 return nil 297 } 298 299 func (h *javaHandle) Kill() error { 300 if err := h.executor.ShutDown(); err != nil { 301 if h.pluginClient.Exited() { 302 return nil 303 } 304 return fmt.Errorf("executor Shutdown failed: %v", err) 305 } 306 307 select { 308 case <-h.doneCh: 309 return nil 310 case <-time.After(h.killTimeout): 311 if h.pluginClient.Exited() { 312 return nil 313 } 314 if err := h.executor.Exit(); err != nil { 315 return fmt.Errorf("executor Exit failed: %v", err) 316 } 317 318 return nil 319 } 320 } 321 322 func (h *javaHandle) run() { 323 ps, err := h.executor.Wait() 324 close(h.doneCh) 325 if ps.ExitCode == 0 && err != nil { 326 if h.isolationConfig != nil { 327 if e := executor.DestroyCgroup(h.isolationConfig.Cgroup); e != nil { 328 h.logger.Printf("[ERR] driver.java: destroying cgroup failed while killing cgroup: %v", e) 329 } 330 } else { 331 if e := killProcess(h.userPid); e != nil { 332 h.logger.Printf("[ERR] driver.java: error killing user process: %v", e) 333 } 334 } 335 if e := h.allocDir.UnmountAll(); e != nil { 336 h.logger.Printf("[ERR] driver.java: unmounting dev,proc and alloc dirs failed: %v", e) 337 } 338 } 339 h.waitCh <- &cstructs.WaitResult{ExitCode: ps.ExitCode, Signal: 0, Err: err} 340 close(h.waitCh) 341 h.pluginClient.Kill() 342 }