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  }