github.com/janma/nomad@v0.11.3/drivers/java/driver_test.go (about)

     1  package java
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
    12  
    13  	"context"
    14  	"time"
    15  
    16  	ctestutil "github.com/hashicorp/nomad/client/testutil"
    17  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    18  	"github.com/hashicorp/nomad/helper/testlog"
    19  	"github.com/hashicorp/nomad/helper/uuid"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  	"github.com/hashicorp/nomad/plugins/drivers"
    22  	"github.com/hashicorp/nomad/testutil"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func javaCompatible(t *testing.T) {
    27  	ctestutil.JavaCompatible(t)
    28  
    29  	_, _, _, err := javaVersionInfo()
    30  	if err != nil {
    31  		t.Skipf("java not found; skipping: %v", err)
    32  	}
    33  }
    34  
    35  func TestJavaDriver_Fingerprint(t *testing.T) {
    36  	javaCompatible(t)
    37  	if !testutil.IsCI() {
    38  		t.Parallel()
    39  	}
    40  
    41  	ctx, cancel := context.WithCancel(context.Background())
    42  	defer cancel()
    43  
    44  	d := NewDriver(ctx, testlog.HCLogger(t))
    45  	harness := dtestutil.NewDriverHarness(t, d)
    46  
    47  	fpCh, err := harness.Fingerprint(context.Background())
    48  	require.NoError(t, err)
    49  
    50  	select {
    51  	case fp := <-fpCh:
    52  		require.Equal(t, drivers.HealthStateHealthy, fp.Health)
    53  		detected, _ := fp.Attributes["driver.java"].GetBool()
    54  		require.True(t, detected)
    55  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
    56  		require.Fail(t, "timeout receiving fingerprint")
    57  	}
    58  }
    59  
    60  func TestJavaDriver_Jar_Start_Wait(t *testing.T) {
    61  	javaCompatible(t)
    62  	if !testutil.IsCI() {
    63  		t.Parallel()
    64  	}
    65  
    66  	require := require.New(t)
    67  	ctx, cancel := context.WithCancel(context.Background())
    68  	defer cancel()
    69  
    70  	d := NewDriver(ctx, testlog.HCLogger(t))
    71  	harness := dtestutil.NewDriverHarness(t, d)
    72  
    73  	tc := &TaskConfig{
    74  		JarPath: "demoapp.jar",
    75  		Args:    []string{"1"},
    76  		JvmOpts: []string{"-Xmx64m", "-Xms32m"},
    77  	}
    78  	task := basicTask(t, "demo-app", tc)
    79  
    80  	cleanup := harness.MkAllocDir(task, true)
    81  	defer cleanup()
    82  
    83  	copyFile("./test-resources/demoapp.jar", filepath.Join(task.TaskDir().Dir, "demoapp.jar"), t)
    84  
    85  	handle, _, err := harness.StartTask(task)
    86  	require.NoError(err)
    87  
    88  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
    89  	require.NoError(err)
    90  	result := <-ch
    91  	require.Nil(result.Err)
    92  
    93  	require.Zero(result.ExitCode)
    94  
    95  	// Get the stdout of the process and assert that it's not empty
    96  	stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "demo-app.stdout.0"))
    97  	require.NoError(err)
    98  	require.Contains(string(stdout), "Hello")
    99  
   100  	require.NoError(harness.DestroyTask(task.ID, true))
   101  }
   102  
   103  func TestJavaDriver_Jar_Stop_Wait(t *testing.T) {
   104  	javaCompatible(t)
   105  	if !testutil.IsCI() {
   106  		t.Parallel()
   107  	}
   108  
   109  	require := require.New(t)
   110  	ctx, cancel := context.WithCancel(context.Background())
   111  	defer cancel()
   112  
   113  	d := NewDriver(ctx, testlog.HCLogger(t))
   114  	harness := dtestutil.NewDriverHarness(t, d)
   115  
   116  	tc := &TaskConfig{
   117  		JarPath: "demoapp.jar",
   118  		Args:    []string{"600"},
   119  		JvmOpts: []string{"-Xmx64m", "-Xms32m"},
   120  	}
   121  	task := basicTask(t, "demo-app", tc)
   122  
   123  	cleanup := harness.MkAllocDir(task, true)
   124  	defer cleanup()
   125  
   126  	copyFile("./test-resources/demoapp.jar", filepath.Join(task.TaskDir().Dir, "demoapp.jar"), t)
   127  
   128  	handle, _, err := harness.StartTask(task)
   129  	require.NoError(err)
   130  
   131  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   132  	require.NoError(err)
   133  
   134  	require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
   135  
   136  	go func() {
   137  		time.Sleep(10 * time.Millisecond)
   138  		harness.StopTask(task.ID, 2*time.Second, "SIGINT")
   139  	}()
   140  
   141  	select {
   142  	case result := <-ch:
   143  		require.False(result.Successful())
   144  	case <-time.After(10 * time.Second):
   145  		require.Fail("timeout waiting for task to shutdown")
   146  	}
   147  
   148  	// Ensure that the task is marked as dead, but account
   149  	// for WaitTask() closing channel before internal state is updated
   150  	testutil.WaitForResult(func() (bool, error) {
   151  		status, err := harness.InspectTask(task.ID)
   152  		if err != nil {
   153  			return false, fmt.Errorf("inspecting task failed: %v", err)
   154  		}
   155  		if status.State != drivers.TaskStateExited {
   156  			return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
   157  		}
   158  
   159  		return true, nil
   160  	}, func(err error) {
   161  		require.NoError(err)
   162  	})
   163  
   164  	require.NoError(harness.DestroyTask(task.ID, true))
   165  }
   166  
   167  func TestJavaDriver_Class_Start_Wait(t *testing.T) {
   168  	javaCompatible(t)
   169  	if !testutil.IsCI() {
   170  		t.Parallel()
   171  	}
   172  
   173  	require := require.New(t)
   174  	ctx, cancel := context.WithCancel(context.Background())
   175  	defer cancel()
   176  
   177  	d := NewDriver(ctx, testlog.HCLogger(t))
   178  	harness := dtestutil.NewDriverHarness(t, d)
   179  
   180  	tc := &TaskConfig{
   181  		Class: "Hello",
   182  		Args:  []string{"1"},
   183  	}
   184  	task := basicTask(t, "demo-app", tc)
   185  
   186  	cleanup := harness.MkAllocDir(task, true)
   187  	defer cleanup()
   188  
   189  	copyFile("./test-resources/Hello.class", filepath.Join(task.TaskDir().Dir, "Hello.class"), t)
   190  
   191  	handle, _, err := harness.StartTask(task)
   192  	require.NoError(err)
   193  
   194  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   195  	require.NoError(err)
   196  	result := <-ch
   197  	require.Nil(result.Err)
   198  
   199  	require.Zero(result.ExitCode)
   200  
   201  	// Get the stdout of the process and assert that it's not empty
   202  	stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "demo-app.stdout.0"))
   203  	require.NoError(err)
   204  	require.Contains(string(stdout), "Hello")
   205  
   206  	require.NoError(harness.DestroyTask(task.ID, true))
   207  }
   208  
   209  func TestJavaCmdArgs(t *testing.T) {
   210  	cases := []struct {
   211  		name     string
   212  		cfg      TaskConfig
   213  		expected []string
   214  	}{
   215  		{
   216  			"jar_path_full",
   217  			TaskConfig{
   218  				JvmOpts: []string{"-Xmx512m", "-Xms128m"},
   219  				JarPath: "/jar-path.jar",
   220  				Args:    []string{"hello", "world"},
   221  			},
   222  			[]string{"-Xmx512m", "-Xms128m", "-jar", "/jar-path.jar", "hello", "world"},
   223  		},
   224  		{
   225  			"class_full",
   226  			TaskConfig{
   227  				JvmOpts:   []string{"-Xmx512m", "-Xms128m"},
   228  				Class:     "ClassName",
   229  				ClassPath: "/classpath",
   230  				Args:      []string{"hello", "world"},
   231  			},
   232  			[]string{"-Xmx512m", "-Xms128m", "-cp", "/classpath", "ClassName", "hello", "world"},
   233  		},
   234  		{
   235  			"jar_path_slim",
   236  			TaskConfig{
   237  				JarPath: "/jar-path.jar",
   238  			},
   239  			[]string{"-jar", "/jar-path.jar"},
   240  		},
   241  		{
   242  			"class_slim",
   243  			TaskConfig{
   244  				Class: "ClassName",
   245  			},
   246  			[]string{"ClassName"},
   247  		},
   248  	}
   249  
   250  	for _, c := range cases {
   251  		t.Run(c.name, func(t *testing.T) {
   252  			found := javaCmdArgs(c.cfg)
   253  			require.Equal(t, c.expected, found)
   254  		})
   255  	}
   256  }
   257  
   258  func TestJavaDriver_ExecTaskStreaming(t *testing.T) {
   259  	javaCompatible(t)
   260  	if !testutil.IsCI() {
   261  		t.Parallel()
   262  	}
   263  
   264  	require := require.New(t)
   265  	ctx, cancel := context.WithCancel(context.Background())
   266  	defer cancel()
   267  
   268  	d := NewDriver(ctx, testlog.HCLogger(t))
   269  	harness := dtestutil.NewDriverHarness(t, d)
   270  	defer harness.Kill()
   271  
   272  	tc := &TaskConfig{
   273  		Class: "Hello",
   274  		Args:  []string{"900"},
   275  	}
   276  	task := basicTask(t, "demo-app", tc)
   277  
   278  	cleanup := harness.MkAllocDir(task, true)
   279  	defer cleanup()
   280  
   281  	copyFile("./test-resources/Hello.class", filepath.Join(task.TaskDir().Dir, "Hello.class"), t)
   282  
   283  	_, _, err := harness.StartTask(task)
   284  	require.NoError(err)
   285  	defer d.DestroyTask(task.ID, true)
   286  
   287  	dtestutil.ExecTaskStreamingConformanceTests(t, harness, task.ID)
   288  
   289  }
   290  func basicTask(t *testing.T, name string, taskConfig *TaskConfig) *drivers.TaskConfig {
   291  	t.Helper()
   292  
   293  	task := &drivers.TaskConfig{
   294  		ID:   uuid.Generate(),
   295  		Name: name,
   296  		Resources: &drivers.Resources{
   297  			NomadResources: &structs.AllocatedTaskResources{
   298  				Memory: structs.AllocatedMemoryResources{
   299  					MemoryMB: 128,
   300  				},
   301  				Cpu: structs.AllocatedCpuResources{
   302  					CpuShares: 100,
   303  				},
   304  			},
   305  			LinuxResources: &drivers.LinuxResources{
   306  				MemoryLimitBytes: 134217728,
   307  				CPUShares:        100,
   308  			},
   309  		},
   310  	}
   311  
   312  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskConfig))
   313  	return task
   314  }
   315  
   316  // copyFile moves an existing file to the destination
   317  func copyFile(src, dst string, t *testing.T) {
   318  	in, err := os.Open(src)
   319  	if err != nil {
   320  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   321  	}
   322  	defer in.Close()
   323  	out, err := os.Create(dst)
   324  	if err != nil {
   325  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   326  	}
   327  	defer func() {
   328  		if err := out.Close(); err != nil {
   329  			t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   330  		}
   331  	}()
   332  	if _, err = io.Copy(out, in); err != nil {
   333  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   334  	}
   335  	if err := out.Sync(); err != nil {
   336  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   337  	}
   338  }
   339  
   340  func TestConfig_ParseAllHCL(t *testing.T) {
   341  	cfgStr := `
   342  config {
   343    class = "java.main"
   344    class_path = "/tmp/cp"
   345    jar_path = "/tmp/jar.jar"
   346    jvm_options = ["-Xmx600"]
   347    args = ["arg1", "arg2"]
   348  }`
   349  
   350  	expected := &TaskConfig{
   351  		Class:     "java.main",
   352  		ClassPath: "/tmp/cp",
   353  		JarPath:   "/tmp/jar.jar",
   354  		JvmOpts:   []string{"-Xmx600"},
   355  		Args:      []string{"arg1", "arg2"},
   356  	}
   357  
   358  	var tc *TaskConfig
   359  	hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc)
   360  
   361  	require.EqualValues(t, expected, tc)
   362  }