github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/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/nomad/client/allocdir" 16 "github.com/hashicorp/nomad/client/config" 17 "github.com/hashicorp/nomad/client/driver/executor" 18 cstructs "github.com/hashicorp/nomad/client/driver/structs" 19 "github.com/hashicorp/nomad/client/fingerprint" 20 "github.com/hashicorp/nomad/client/getter" 21 "github.com/hashicorp/nomad/nomad/structs" 22 "github.com/mitchellh/mapstructure" 23 ) 24 25 // JavaDriver is a simple driver to execute applications packaged in Jars. 26 // It literally just fork/execs tasks with the java command. 27 type JavaDriver struct { 28 DriverContext 29 fingerprint.StaticFingerprinter 30 } 31 32 type JavaDriverConfig struct { 33 JvmOpts []string `mapstructure:"jvm_options"` 34 ArtifactSource string `mapstructure:"artifact_source"` 35 Checksum string `mapstructure:"checksum"` 36 Args []string `mapstructure:"args"` 37 } 38 39 // javaHandle is returned from Start/Open as a handle to the PID 40 type javaHandle struct { 41 cmd executor.Executor 42 killTimeout time.Duration 43 logger *log.Logger 44 waitCh chan *cstructs.WaitResult 45 doneCh chan struct{} 46 } 47 48 // NewJavaDriver is used to create a new exec driver 49 func NewJavaDriver(ctx *DriverContext) Driver { 50 return &JavaDriver{DriverContext: *ctx} 51 } 52 53 func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { 54 // Only enable if we are root when running on non-windows systems. 55 if runtime.GOOS == "linux" && syscall.Geteuid() != 0 { 56 d.logger.Printf("[DEBUG] driver.java: must run as root user on linux, disabling") 57 return false, nil 58 } 59 60 // Find java version 61 var out bytes.Buffer 62 var erOut bytes.Buffer 63 cmd := exec.Command("java", "-version") 64 cmd.Stdout = &out 65 cmd.Stderr = &erOut 66 err := cmd.Run() 67 if err != nil { 68 // assume Java wasn't found 69 return false, nil 70 } 71 72 // 'java -version' returns output on Stderr typically. 73 // Check stdout, but it's probably empty 74 var infoString string 75 if out.String() != "" { 76 infoString = out.String() 77 } 78 79 if erOut.String() != "" { 80 infoString = erOut.String() 81 } 82 83 if infoString == "" { 84 d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting") 85 return false, nil 86 } 87 88 // Assume 'java -version' returns 3 lines: 89 // java version "1.6.0_36" 90 // OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04) 91 // OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode) 92 // Each line is terminated by \n 93 info := strings.Split(infoString, "\n") 94 versionString := info[0] 95 versionString = strings.TrimPrefix(versionString, "java version ") 96 versionString = strings.Trim(versionString, "\"") 97 node.Attributes["driver.java"] = "1" 98 node.Attributes["driver.java.version"] = versionString 99 node.Attributes["driver.java.runtime"] = info[1] 100 node.Attributes["driver.java.vm"] = info[2] 101 102 return true, nil 103 } 104 105 func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { 106 var driverConfig JavaDriverConfig 107 if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { 108 return nil, err 109 } 110 taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName] 111 if !ok { 112 return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName) 113 } 114 115 // Proceed to download an artifact to be executed. 116 path, err := getter.GetArtifact( 117 filepath.Join(taskDir, allocdir.TaskLocal), 118 driverConfig.ArtifactSource, 119 driverConfig.Checksum, 120 d.logger, 121 ) 122 if err != nil { 123 return nil, err 124 } 125 126 jarName := filepath.Base(path) 127 128 args := []string{} 129 // Look for jvm options 130 if len(driverConfig.JvmOpts) != 0 { 131 d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts) 132 args = append(args, driverConfig.JvmOpts...) 133 } 134 135 // Build the argument list. 136 args = append(args, "-jar", filepath.Join(allocdir.TaskLocal, jarName)) 137 if len(driverConfig.Args) != 0 { 138 args = append(args, driverConfig.Args...) 139 } 140 141 // Setup the command 142 // Assumes Java is in the $PATH, but could probably be detected 143 execCtx := executor.NewExecutorContext(d.taskEnv) 144 cmd := executor.Command(execCtx, "java", args...) 145 146 // Populate environment variables 147 cmd.Command().Env = d.taskEnv.EnvList() 148 149 if err := cmd.Limit(task.Resources); err != nil { 150 return nil, fmt.Errorf("failed to constrain resources: %s", err) 151 } 152 153 if err := cmd.ConfigureTaskDir(d.taskName, ctx.AllocDir); err != nil { 154 return nil, fmt.Errorf("failed to configure task directory: %v", err) 155 } 156 157 if err := cmd.Start(); err != nil { 158 return nil, fmt.Errorf("failed to start source: %v", err) 159 } 160 161 // Return a driver handle 162 h := &javaHandle{ 163 cmd: cmd, 164 killTimeout: d.DriverContext.KillTimeout(task), 165 logger: d.logger, 166 doneCh: make(chan struct{}), 167 waitCh: make(chan *cstructs.WaitResult, 1), 168 } 169 170 go h.run() 171 return h, nil 172 } 173 174 type javaId struct { 175 ExecutorId string 176 KillTimeout time.Duration 177 } 178 179 func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) { 180 id := &javaId{} 181 if err := json.Unmarshal([]byte(handleID), id); err != nil { 182 return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err) 183 } 184 185 // Find the process 186 execCtx := executor.NewExecutorContext(d.taskEnv) 187 cmd, err := executor.OpenId(execCtx, id.ExecutorId) 188 if err != nil { 189 return nil, fmt.Errorf("failed to open ID %v: %v", id.ExecutorId, err) 190 } 191 192 // Return a driver handle 193 h := &javaHandle{ 194 cmd: cmd, 195 logger: d.logger, 196 killTimeout: id.KillTimeout, 197 doneCh: make(chan struct{}), 198 waitCh: make(chan *cstructs.WaitResult, 1), 199 } 200 201 go h.run() 202 return h, nil 203 } 204 205 func (h *javaHandle) ID() string { 206 executorId, _ := h.cmd.ID() 207 id := javaId{ 208 ExecutorId: executorId, 209 KillTimeout: h.killTimeout, 210 } 211 212 data, err := json.Marshal(id) 213 if err != nil { 214 h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err) 215 } 216 return string(data) 217 } 218 219 func (h *javaHandle) WaitCh() chan *cstructs.WaitResult { 220 return h.waitCh 221 } 222 223 func (h *javaHandle) Update(task *structs.Task) error { 224 // Update is not possible 225 return nil 226 } 227 228 func (h *javaHandle) Kill() error { 229 h.cmd.Shutdown() 230 select { 231 case <-h.doneCh: 232 return nil 233 case <-time.After(h.killTimeout): 234 return h.cmd.ForceStop() 235 } 236 } 237 238 func (h *javaHandle) run() { 239 res := h.cmd.Wait() 240 close(h.doneCh) 241 h.waitCh <- res 242 close(h.waitCh) 243 }