github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/client/driver/java.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/hashicorp/nomad/client/allocdir" 18 "github.com/hashicorp/nomad/client/config" 19 "github.com/hashicorp/nomad/client/executor" 20 "github.com/hashicorp/nomad/nomad/structs" 21 ) 22 23 // JavaDriver is a simple driver to execute applications packaged in Jars. 24 // It literally just fork/execs tasks with the java command. 25 type JavaDriver struct { 26 DriverContext 27 } 28 29 // javaHandle is returned from Start/Open as a handle to the PID 30 type javaHandle struct { 31 cmd executor.Executor 32 waitCh chan error 33 doneCh chan struct{} 34 } 35 36 // NewJavaDriver is used to create a new exec driver 37 func NewJavaDriver(ctx *DriverContext) Driver { 38 return &JavaDriver{*ctx} 39 } 40 41 func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 42 // Only enable if we are root when running on non-windows systems. 43 if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { 44 d.logger.Printf("[DEBUG] driver.java: must run as root user, disabling") 45 return false, nil 46 } 47 48 // Find java version 49 var out bytes.Buffer 50 var erOut bytes.Buffer 51 cmd := exec.Command("java", "-version") 52 cmd.Stdout = &out 53 cmd.Stderr = &erOut 54 err := cmd.Run() 55 if err != nil { 56 // assume Java wasn't found 57 return false, nil 58 } 59 60 // 'java -version' returns output on Stderr typically. 61 // Check stdout, but it's probably empty 62 var infoString string 63 if out.String() != "" { 64 infoString = out.String() 65 } 66 67 if erOut.String() != "" { 68 infoString = erOut.String() 69 } 70 71 if infoString == "" { 72 d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting") 73 return false, nil 74 } 75 76 // Assume 'java -version' returns 3 lines: 77 // java version "1.6.0_36" 78 // OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04) 79 // OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode) 80 // Each line is terminated by \n 81 info := strings.Split(infoString, "\n") 82 versionString := info[0] 83 versionString = strings.TrimPrefix(versionString, "java version ") 84 versionString = strings.Trim(versionString, "\"") 85 node.Attributes["driver.java"] = "1" 86 node.Attributes["driver.java.version"] = versionString 87 node.Attributes["driver.java.runtime"] = info[1] 88 node.Attributes["driver.java.vm"] = info[2] 89 90 return true, nil 91 } 92 93 func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 94 // Get the jar source 95 source, ok := task.Config["jar_source"] 96 if !ok || source == "" { 97 return nil, fmt.Errorf("missing jar source for Java Jar driver") 98 } 99 100 // Attempt to download the thing 101 // Should be extracted to some kind of Http Fetcher 102 // Right now, assume publicly accessible HTTP url 103 resp, err := http.Get(source) 104 if err != nil { 105 return nil, fmt.Errorf("Error downloading source for Java driver: %s", err) 106 } 107 108 // Get the tasks local directory. 109 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 110 if !ok { 111 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 112 } 113 taskLocal := filepath.Join(taskDir, allocdir.TaskLocal) 114 115 // Create a location to download the binary. 116 fName := path.Base(source) 117 fPath := filepath.Join(taskLocal, fName) 118 f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY, 0666) 119 if err != nil { 120 return nil, fmt.Errorf("Error opening file to download to: %s", err) 121 } 122 123 defer f.Close() 124 defer resp.Body.Close() 125 126 // Copy remote file to local directory for execution 127 // TODO: a retry of sort if io.Copy fails, for large binaries 128 _, ioErr := io.Copy(f, resp.Body) 129 if ioErr != nil { 130 return nil, fmt.Errorf("Error copying jar from source: %s", ioErr) 131 } 132 133 // Get the environment variables. 134 envVars := TaskEnvironmentVariables(ctx, task) 135 136 args := []string{} 137 // Look for jvm options 138 jvm_options, ok := task.Config["jvm_options"] 139 if ok && jvm_options != "" { 140 d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", jvm_options) 141 args = append(args, jvm_options) 142 } 143 144 // Build the argument list 145 args = append(args, "-jar", filepath.Join(allocdir.TaskLocal, fName)) 146 147 // Build the argument list. 148 if argRaw, ok := task.Config["args"]; ok { 149 args = append(args, argRaw) 150 } 151 152 // Setup the command 153 // Assumes Java is in the $PATH, but could probably be detected 154 cmd := executor.Command("java", args...) 155 156 // Populate environment variables 157 cmd.Command().Env = envVars.List() 158 159 if err := cmd.Limit(task.Resources); err != nil { 160 return nil, fmt.Errorf("failed to constrain resources: %s", err) 161 } 162 163 if err := cmd.ConfigureTaskDir(d.taskName, ctx.AllocDir); err != nil { 164 return nil, fmt.Errorf("failed to configure task directory: %v", err) 165 } 166 167 if err := cmd.Start(); err != nil { 168 return nil, fmt.Errorf("failed to start source: %v", err) 169 } 170 171 // Return a driver handle 172 h := &javaHandle{ 173 cmd: cmd, 174 doneCh: make(chan struct{}), 175 waitCh: make(chan error, 1), 176 } 177 178 go h.run() 179 return h, nil 180 } 181 182 func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 183 // Find the process 184 cmd, err := executor.OpenId(handleID) 185 if err != nil { 186 return nil, fmt.Errorf("failed to open ID %v: %v", handleID, err) 187 } 188 189 // Return a driver handle 190 h := &javaHandle{ 191 cmd: cmd, 192 doneCh: make(chan struct{}), 193 waitCh: make(chan error, 1), 194 } 195 196 go h.run() 197 return h, nil 198 } 199 200 func (h *javaHandle) ID() string { 201 id, _ := h.cmd.ID() 202 return id 203 } 204 205 func (h *javaHandle) WaitCh() chan error { 206 return h.waitCh 207 } 208 209 func (h *javaHandle) Update(task *structs.Task) error { 210 // Update is not possible 211 return nil 212 } 213 214 func (h *javaHandle) Kill() error { 215 h.cmd.Shutdown() 216 select { 217 case <-h.doneCh: 218 return nil 219 case <-time.After(5 * time.Second): 220 return h.cmd.ForceStop() 221 } 222 } 223 224 func (h *javaHandle) run() { 225 err := h.cmd.Wait() 226 close(h.doneCh) 227 if err != nil { 228 h.waitCh <- err 229 } 230 close(h.waitCh) 231 }