github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/drivers/java/driver_test.go (about)

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