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 }