Merge pull request #2199 from hashicorp/f-jars

Support setting class_path and class name.
This commit is contained in:
Alex Dadgar
2017-01-17 15:25:47 -08:00
committed by GitHub
7 changed files with 168 additions and 16 deletions

View File

@@ -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"
@@ -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
@@ -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,
@@ -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
@@ -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...)
}

View File

@@ -0,0 +1,7 @@
package driver
import cstructs "github.com/hashicorp/nomad/client/structs"
func (d *JavaDriver) FSIsolation() cstructs.FSIsolation {
return cstructs.FSIsolationChroot
}

View File

@@ -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)
}
}

View File

@@ -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 not shown.

View File

@@ -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) {

View File

@@ -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`).
@@ -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