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  }