Skip to content

Commit

Permalink
Merge pull request #2199 from hashicorp/f-jars
Browse files Browse the repository at this point in the history
Support setting class_path and class name.
  • Loading branch information
dadgar committed Jan 17, 2017
2 parents 38fd66f + 9206446 commit 11200cd
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 16 deletions.
66 changes: 52 additions & 14 deletions client/driver/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/mitchellh/mapstructure"

"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/env"
"github.com/hashicorp/nomad/client/driver/executor"
dstructs "github.com/hashicorp/nomad/client/driver/structs"
"github.com/hashicorp/nomad/client/fingerprint"
Expand All @@ -40,9 +41,11 @@ type JavaDriver struct {
}

type JavaDriverConfig struct {
JarPath string `mapstructure:"jar_path"`
JvmOpts []string `mapstructure:"jvm_options"`
Args []string `mapstructure:"args"`
Class string `mapstructure:"class"`
ClassPath string `mapstructure:"class_path"`
JarPath string `mapstructure:"jar_path"`
JvmOpts []string `mapstructure:"jvm_options"`
Args []string `mapstructure:"args"`
}

// javaHandle is returned from Start/Open as a handle to the PID
Expand Down Expand Up @@ -70,9 +73,14 @@ func (d *JavaDriver) Validate(config map[string]interface{}) error {
fd := &fields.FieldData{
Raw: config,
Schema: map[string]*fields.FieldSchema{
"class": &fields.FieldSchema{
Type: fields.TypeString,
},
"class_path": &fields.FieldSchema{
Type: fields.TypeString,
},
"jar_path": &fields.FieldSchema{
Type: fields.TypeString,
Required: true,
Type: fields.TypeString,
},
"jvm_options": &fields.FieldSchema{
Type: fields.TypeArray,
Expand All @@ -96,10 +104,6 @@ func (d *JavaDriver) Abilities() DriverAbilities {
}
}

func (d *JavaDriver) FSIsolation() cstructs.FSIsolation {
return cstructs.FSIsolationChroot
}

func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
// Get the current status so that we can log any debug messages only if the
// state changes
Expand Down Expand Up @@ -167,25 +171,59 @@ func (d *JavaDriver) Prestart(ctx *ExecContext, task *structs.Task) error {
return nil
}

func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
func NewJavaDriverConfig(task *structs.Task, env *env.TaskEnvironment) (*JavaDriverConfig, error) {
var driverConfig JavaDriverConfig
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
return nil, err
}

if driverConfig.JarPath == "" {
return nil, fmt.Errorf("jar_path must be specified")
// Interpolate everything
driverConfig.Class = env.ReplaceEnv(driverConfig.Class)
driverConfig.ClassPath = env.ReplaceEnv(driverConfig.ClassPath)
driverConfig.JarPath = env.ReplaceEnv(driverConfig.JarPath)
driverConfig.JvmOpts = env.ParseAndReplace(driverConfig.JvmOpts)
driverConfig.Args = env.ParseAndReplace(driverConfig.Args)

// Validate
jarSpecified := driverConfig.JarPath != ""
classSpecified := driverConfig.Class != ""
if !jarSpecified && !classSpecified {
return nil, fmt.Errorf("jar_path or class must be specified")
}

return &driverConfig, nil
}

func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
driverConfig, err := NewJavaDriverConfig(task, d.taskEnv)
if err != nil {
return nil, err
}

args := []string{}

// Look for jvm options
if len(driverConfig.JvmOpts) != 0 {
d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts)
args = append(args, driverConfig.JvmOpts...)
}

// Build the argument list.
args = append(args, "-jar", driverConfig.JarPath)
// Add the classpath
if driverConfig.ClassPath != "" {
args = append(args, "-cp", driverConfig.ClassPath)
}

// Add the jar
if driverConfig.JarPath != "" {
args = append(args, "-jar", driverConfig.JarPath)
}

// Add the class
if driverConfig.Class != "" {
args = append(args, driverConfig.Class)
}

// Add any args
if len(driverConfig.Args) != 0 {
args = append(args, driverConfig.Args...)
}
Expand Down
7 changes: 7 additions & 0 deletions client/driver/java_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package driver

import cstructs "github.com/hashicorp/nomad/client/structs"

func (d *JavaDriver) FSIsolation() cstructs.FSIsolation {
return cstructs.FSIsolationChroot
}
67 changes: 67 additions & 0 deletions client/driver/java_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,70 @@ func TestJavaDriverUser(t *testing.T) {
t.Fatalf("Expecting '%v' in '%v'", msg, err)
}
}

func TestJavaDriver_Start_Wait_Class(t *testing.T) {
if !javaLocated() {
t.Skip("Java not found; skipping")
}

ctestutils.JavaCompatible(t)
task := &structs.Task{
Name: "demo-app",
Driver: "java",
Config: map[string]interface{}{
"class_path": "${NOMAD_TASK_DIR}",
"class": "Hello",
},
LogConfig: &structs.LogConfig{
MaxFiles: 10,
MaxFileSizeMB: 10,
},
Resources: basicResources,
}

ctx := testDriverContexts(t, task)
//defer ctx.AllocDir.Destroy()
d := NewJavaDriver(ctx.DriverCtx)

// Copy the test jar into the task's directory
dst := ctx.ExecCtx.TaskDir.LocalDir
copyFile("./test-resources/java/Hello.class", filepath.Join(dst, "Hello.class"), t)

if err := d.Prestart(ctx.ExecCtx, task); err != nil {
t.Fatalf("prestart err: %v", err)
}
handle, err := d.Start(ctx.ExecCtx, task)
if err != nil {
t.Fatalf("err: %v", err)
}
if handle == nil {
t.Fatalf("missing handle")
}

// Task should terminate quickly
select {
case res := <-handle.WaitCh():
if !res.Successful() {
t.Fatalf("err: %v", res)
}
case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
// expect the timeout b/c it's a long lived process
break
}

// Get the stdout of the process and assrt that it's not empty
stdout := filepath.Join(ctx.ExecCtx.TaskDir.LogDir, "demo-app.stdout.0")
fInfo, err := os.Stat(stdout)
if err != nil {
t.Fatalf("failed to get stdout of process: %v", err)
}
if fInfo.Size() == 0 {
t.Fatalf("stdout of process is empty")
}

// need to kill long lived process
err = handle.Kill()
if err != nil {
t.Fatalf("Error: %s", err)
}
}
9 changes: 9 additions & 0 deletions client/driver/java_universal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build !linux

package driver

import cstructs "github.com/hashicorp/nomad/client/structs"

func (d *JavaDriver) FSIsolation() cstructs.FSIsolation {
return cstructs.FSIsolationNone
}
Binary file added client/driver/test-resources/java/Hello.class
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
public class Hello {
public static void main(String[] args) {
while (true) {
System.out.println("Hi");
System.out.println("Hello");
try {
Thread.sleep(1000); //1000 milliseconds is one second.
} catch(InterruptedException ex) {
Expand Down
33 changes: 32 additions & 1 deletion website/source/docs/drivers/java.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ task "webservice" {

The `java` driver supports the following configuration in the job spec:

* `jar_path` - The path to the downloaded Jar. In most cases this will just be
* `class` - (Optional) The name of the class to run. If `jar_path` is specified
and the manifest specifies a main class, this is optional. If shipping classes
rather than a Jar, please specify the class to run and the `class_path`.

* `class_path` - (Optional) The `class_path` specifies the clath path used by
Java to lookup classes and Jars.

* `jar_path` - (Optional) The path to the downloaded Jar. In most cases this will just be
the name of the Jar. However, if the supplied artifact is an archive that
contains the Jar in a subfolder, the path will need to be the relative path
(`subdir/from_archive/my.jar`).
Expand Down Expand Up @@ -67,6 +74,30 @@ task "web" {
}
```

A simple config block to run a Java class:

```hcl
task "web" {
driver = "java"
config {
class = "Hello"
class_path = "${NOMAD_TASK_DIR}"
jvm_options = ["-Xmx2048m", "-Xms256m"]
}
# Specifying an artifact is required with the "java" driver. This is the
# mechanism to ship the Jar to be run.
artifact {
source = "https://internal.file.server/Hello.class"
options {
checksum = "md5:123445555555555"
}
}
}
```

## Client Requirements

The `java` driver requires Java to be installed and in your system's `$PATH`. On
Expand Down

0 comments on commit 11200cd

Please sign in to comment.