github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/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/go-multierror"
    16  	"github.com/hashicorp/go-plugin"
    17  	"github.com/mitchellh/mapstructure"
    18  
    19  	"github.com/hashicorp/nomad/client/allocdir"
    20  	"github.com/hashicorp/nomad/client/config"
    21  	"github.com/hashicorp/nomad/client/driver/executor"
    22  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    23  	"github.com/hashicorp/nomad/client/fingerprint"
    24  	"github.com/hashicorp/nomad/client/getter"
    25  	"github.com/hashicorp/nomad/helper/discover"
    26  	"github.com/hashicorp/nomad/nomad/structs"
    27  )
    28  
    29  // JavaDriver is a simple driver to execute applications packaged in Jars.
    30  // It literally just fork/execs tasks with the java command.
    31  type JavaDriver struct {
    32  	DriverContext
    33  	fingerprint.StaticFingerprinter
    34  }
    35  
    36  type JavaDriverConfig struct {
    37  	JvmOpts        []string `mapstructure:"jvm_options"`
    38  	ArtifactSource string   `mapstructure:"artifact_source"`
    39  	Checksum       string   `mapstructure:"checksum"`
    40  	Args           []string `mapstructure:"args"`
    41  }
    42  
    43  // javaHandle is returned from Start/Open as a handle to the PID
    44  type javaHandle struct {
    45  	pluginClient    *plugin.Client
    46  	userPid         int
    47  	executor        executor.Executor
    48  	isolationConfig *cstructs.IsolationConfig
    49  
    50  	taskDir     string
    51  	allocDir    *allocdir.AllocDir
    52  	killTimeout time.Duration
    53  	version     string
    54  	logger      *log.Logger
    55  	waitCh      chan *cstructs.WaitResult
    56  	doneCh      chan struct{}
    57  }
    58  
    59  // NewJavaDriver is used to create a new exec driver
    60  func NewJavaDriver(ctx *DriverContext) Driver {
    61  	return &JavaDriver{DriverContext: *ctx}
    62  }
    63  
    64  func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    65  	// Only enable if we are root and cgroups are mounted when running on linux systems.
    66  	if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !d.cgroupsMounted(node)) {
    67  		d.logger.Printf("[DEBUG] driver.java: must run as root user on linux, disabling")
    68  		return false, nil
    69  	}
    70  
    71  	// Find java version
    72  	var out bytes.Buffer
    73  	var erOut bytes.Buffer
    74  	cmd := exec.Command("java", "-version")
    75  	cmd.Stdout = &out
    76  	cmd.Stderr = &erOut
    77  	err := cmd.Run()
    78  	if err != nil {
    79  		// assume Java wasn't found
    80  		return false, nil
    81  	}
    82  
    83  	// 'java -version' returns output on Stderr typically.
    84  	// Check stdout, but it's probably empty
    85  	var infoString string
    86  	if out.String() != "" {
    87  		infoString = out.String()
    88  	}
    89  
    90  	if erOut.String() != "" {
    91  		infoString = erOut.String()
    92  	}
    93  
    94  	if infoString == "" {
    95  		d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting")
    96  		return false, nil
    97  	}
    98  
    99  	// Assume 'java -version' returns 3 lines:
   100  	//    java version "1.6.0_36"
   101  	//    OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04)
   102  	//    OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
   103  	// Each line is terminated by \n
   104  	info := strings.Split(infoString, "\n")
   105  	versionString := info[0]
   106  	versionString = strings.TrimPrefix(versionString, "java version ")
   107  	versionString = strings.Trim(versionString, "\"")
   108  	node.Attributes["driver.java"] = "1"
   109  	node.Attributes["driver.java.version"] = versionString
   110  	node.Attributes["driver.java.runtime"] = info[1]
   111  	node.Attributes["driver.java.vm"] = info[2]
   112  
   113  	return true, nil
   114  }
   115  
   116  func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
   117  	var driverConfig JavaDriverConfig
   118  	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
   119  		return nil, err
   120  	}
   121  	taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName]
   122  	if !ok {
   123  		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
   124  	}
   125  
   126  	// Proceed to download an artifact to be executed.
   127  	path, err := getter.GetArtifact(
   128  		taskDir,
   129  		driverConfig.ArtifactSource,
   130  		driverConfig.Checksum,
   131  		d.logger,
   132  	)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	jarName := filepath.Base(path)
   138  
   139  	args := []string{}
   140  	// Look for jvm options
   141  	if len(driverConfig.JvmOpts) != 0 {
   142  		d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts)
   143  		args = append(args, driverConfig.JvmOpts...)
   144  	}
   145  
   146  	// Build the argument list.
   147  	args = append(args, "-jar", jarName)
   148  	if len(driverConfig.Args) != 0 {
   149  		args = append(args, driverConfig.Args...)
   150  	}
   151  
   152  	bin, err := discover.NomadExecutable()
   153  	if err != nil {
   154  		return nil, fmt.Errorf("unable to find the nomad binary: %v", err)
   155  	}
   156  
   157  	pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name))
   158  	pluginConfig := &plugin.ClientConfig{
   159  		Cmd: exec.Command(bin, "executor", pluginLogFile),
   160  	}
   161  
   162  	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	executorCtx := &executor.ExecutorContext{
   167  		TaskEnv:          d.taskEnv,
   168  		AllocDir:         ctx.AllocDir,
   169  		TaskName:         task.Name,
   170  		TaskResources:    task.Resources,
   171  		LogConfig:        task.LogConfig,
   172  		FSIsolation:      true,
   173  		UnprivilegedUser: true,
   174  		ResourceLimits:   true,
   175  	}
   176  
   177  	ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: "java", Args: args}, executorCtx)
   178  	if err != nil {
   179  		pluginClient.Kill()
   180  		return nil, fmt.Errorf("error starting process via the plugin: %v", err)
   181  	}
   182  	d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid)
   183  
   184  	// Return a driver handle
   185  	h := &javaHandle{
   186  		pluginClient:    pluginClient,
   187  		executor:        exec,
   188  		userPid:         ps.Pid,
   189  		isolationConfig: ps.IsolationConfig,
   190  		taskDir:         taskDir,
   191  		allocDir:        ctx.AllocDir,
   192  		killTimeout:     d.DriverContext.KillTimeout(task),
   193  		version:         d.config.Version,
   194  		logger:          d.logger,
   195  		doneCh:          make(chan struct{}),
   196  		waitCh:          make(chan *cstructs.WaitResult, 1),
   197  	}
   198  
   199  	go h.run()
   200  	return h, nil
   201  }
   202  
   203  // cgroupsMounted returns true if the cgroups are mounted on a system otherwise
   204  // returns false
   205  func (d *JavaDriver) cgroupsMounted(node *structs.Node) bool {
   206  	_, ok := node.Attributes["unique.cgroup.mountpoint"]
   207  	return ok
   208  }
   209  
   210  type javaId struct {
   211  	Version         string
   212  	KillTimeout     time.Duration
   213  	PluginConfig    *PluginReattachConfig
   214  	IsolationConfig *cstructs.IsolationConfig
   215  	TaskDir         string
   216  	AllocDir        *allocdir.AllocDir
   217  	UserPid         int
   218  }
   219  
   220  func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   221  	id := &javaId{}
   222  	if err := json.Unmarshal([]byte(handleID), id); err != nil {
   223  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
   224  	}
   225  
   226  	pluginConfig := &plugin.ClientConfig{
   227  		Reattach: id.PluginConfig.PluginConfig(),
   228  	}
   229  	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
   230  	if err != nil {
   231  		merrs := new(multierror.Error)
   232  		merrs.Errors = append(merrs.Errors, err)
   233  		d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid")
   234  		if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil {
   235  			merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e))
   236  		}
   237  		if id.IsolationConfig != nil {
   238  			if e := executor.DestroyCgroup(id.IsolationConfig.Cgroup); e != nil {
   239  				merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying cgroup failed: %v", e))
   240  			}
   241  		}
   242  		if e := ctx.AllocDir.UnmountAll(); e != nil {
   243  			merrs.Errors = append(merrs.Errors, e)
   244  		}
   245  
   246  		return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil())
   247  	}
   248  
   249  	// Return a driver handle
   250  	h := &javaHandle{
   251  		pluginClient:    pluginClient,
   252  		executor:        exec,
   253  		userPid:         id.UserPid,
   254  		isolationConfig: id.IsolationConfig,
   255  		taskDir:         id.TaskDir,
   256  		allocDir:        id.AllocDir,
   257  		logger:          d.logger,
   258  		version:         id.Version,
   259  		killTimeout:     id.KillTimeout,
   260  		doneCh:          make(chan struct{}),
   261  		waitCh:          make(chan *cstructs.WaitResult, 1),
   262  	}
   263  
   264  	go h.run()
   265  	return h, nil
   266  }
   267  
   268  func (h *javaHandle) ID() string {
   269  	id := javaId{
   270  		Version:         h.version,
   271  		KillTimeout:     h.killTimeout,
   272  		PluginConfig:    NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
   273  		UserPid:         h.userPid,
   274  		TaskDir:         h.taskDir,
   275  		AllocDir:        h.allocDir,
   276  		IsolationConfig: h.isolationConfig,
   277  	}
   278  
   279  	data, err := json.Marshal(id)
   280  	if err != nil {
   281  		h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err)
   282  	}
   283  	return string(data)
   284  }
   285  
   286  func (h *javaHandle) WaitCh() chan *cstructs.WaitResult {
   287  	return h.waitCh
   288  }
   289  
   290  func (h *javaHandle) Update(task *structs.Task) error {
   291  	// Store the updated kill timeout.
   292  	h.killTimeout = task.KillTimeout
   293  	h.executor.UpdateLogConfig(task.LogConfig)
   294  
   295  	// Update is not possible
   296  	return nil
   297  }
   298  
   299  func (h *javaHandle) Kill() error {
   300  	if err := h.executor.ShutDown(); err != nil {
   301  		if h.pluginClient.Exited() {
   302  			return nil
   303  		}
   304  		return fmt.Errorf("executor Shutdown failed: %v", err)
   305  	}
   306  
   307  	select {
   308  	case <-h.doneCh:
   309  		return nil
   310  	case <-time.After(h.killTimeout):
   311  		if h.pluginClient.Exited() {
   312  			return nil
   313  		}
   314  		if err := h.executor.Exit(); err != nil {
   315  			return fmt.Errorf("executor Exit failed: %v", err)
   316  		}
   317  
   318  		return nil
   319  	}
   320  }
   321  
   322  func (h *javaHandle) run() {
   323  	ps, err := h.executor.Wait()
   324  	close(h.doneCh)
   325  	if ps.ExitCode == 0 && err != nil {
   326  		if h.isolationConfig != nil {
   327  			if e := executor.DestroyCgroup(h.isolationConfig.Cgroup); e != nil {
   328  				h.logger.Printf("[ERR] driver.java: destroying cgroup failed while killing cgroup: %v", e)
   329  			}
   330  		} else {
   331  			if e := killProcess(h.userPid); e != nil {
   332  				h.logger.Printf("[ERR] driver.java: error killing user process: %v", e)
   333  			}
   334  		}
   335  		if e := h.allocDir.UnmountAll(); e != nil {
   336  			h.logger.Printf("[ERR] driver.java: unmounting dev,proc and alloc dirs failed: %v", e)
   337  		}
   338  	}
   339  	h.waitCh <- &cstructs.WaitResult{ExitCode: ps.ExitCode, Signal: 0, Err: err}
   340  	close(h.waitCh)
   341  	h.pluginClient.Kill()
   342  }