github.com/jmitchell/nomad@v0.1.3-0.20151007230021-7ab84c2862d8/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] 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  	// Build the argument list.
   137  	args := []string{"-jar", filepath.Join(allocdir.TaskLocal, fName)}
   138  	if argRaw, ok := task.Config["args"]; ok {
   139  		args = append(args, argRaw)
   140  	}
   141  
   142  	// Setup the command
   143  	// Assumes Java is in the $PATH, but could probably be detected
   144  	cmd := executor.Command("java", args...)
   145  
   146  	// Populate environment variables
   147  	cmd.Command().Env = envVars.List()
   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  		doneCh: make(chan struct{}),
   165  		waitCh: make(chan error, 1),
   166  	}
   167  
   168  	go h.run()
   169  	return h, nil
   170  }
   171  
   172  func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   173  	// Find the process
   174  	cmd, err := executor.OpenId(handleID)
   175  	if err != nil {
   176  		return nil, fmt.Errorf("failed to open ID %v: %v", handleID, err)
   177  	}
   178  
   179  	// Return a driver handle
   180  	h := &javaHandle{
   181  		cmd:    cmd,
   182  		doneCh: make(chan struct{}),
   183  		waitCh: make(chan error, 1),
   184  	}
   185  
   186  	go h.run()
   187  	return h, nil
   188  }
   189  
   190  func (h *javaHandle) ID() string {
   191  	id, _ := h.cmd.ID()
   192  	return id
   193  }
   194  
   195  func (h *javaHandle) WaitCh() chan error {
   196  	return h.waitCh
   197  }
   198  
   199  func (h *javaHandle) Update(task *structs.Task) error {
   200  	// Update is not possible
   201  	return nil
   202  }
   203  
   204  func (h *javaHandle) Kill() error {
   205  	h.cmd.Shutdown()
   206  	select {
   207  	case <-h.doneCh:
   208  		return nil
   209  	case <-time.After(5 * time.Second):
   210  		return h.cmd.ForceStop()
   211  	}
   212  }
   213  
   214  func (h *javaHandle) run() {
   215  	err := h.cmd.Wait()
   216  	close(h.doneCh)
   217  	if err != nil {
   218  		h.waitCh <- err
   219  	}
   220  	close(h.waitCh)
   221  }