Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support setting class_path and class name. #2199

Merged
merged 1 commit into from
Jan 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, great catch. Thanks!

}
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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋

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