github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/drivers/java/driver_test.go (about) 1 package java 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "github.com/hashicorp/nomad/client/lib/cgutil" 8 "io" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "testing" 13 "time" 14 15 "github.com/hashicorp/nomad/ci" 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 dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" 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 ci.Parallel(t) 38 javaCompatible(t) 39 40 ctx, cancel := context.WithCancel(context.Background()) 41 defer cancel() 42 43 d := NewDriver(ctx, testlog.HCLogger(t)) 44 harness := dtestutil.NewDriverHarness(t, d) 45 46 fpCh, err := harness.Fingerprint(context.Background()) 47 require.NoError(t, err) 48 49 select { 50 case fp := <-fpCh: 51 require.Equal(t, drivers.HealthStateHealthy, fp.Health) 52 detected, _ := fp.Attributes["driver.java"].GetBool() 53 require.True(t, detected) 54 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 55 require.Fail(t, "timeout receiving fingerprint") 56 } 57 } 58 59 func TestJavaDriver_Jar_Start_Wait(t *testing.T) { 60 ci.Parallel(t) 61 javaCompatible(t) 62 63 ctx, cancel := context.WithCancel(context.Background()) 64 defer cancel() 65 66 d := NewDriver(ctx, testlog.HCLogger(t)) 67 harness := dtestutil.NewDriverHarness(t, d) 68 69 tc := &TaskConfig{ 70 JarPath: "demoapp.jar", 71 Args: []string{"1"}, 72 JvmOpts: []string{"-Xmx64m", "-Xms32m"}, 73 } 74 75 task := basicTask(t, "demo-app", tc) 76 77 cleanup := harness.MkAllocDir(task, true) 78 defer cleanup() 79 80 copyFile("./test-resources/demoapp.jar", filepath.Join(task.TaskDir().Dir, "demoapp.jar"), t) 81 82 handle, _, err := harness.StartTask(task) 83 require.NoError(t, err) 84 85 ch, err := harness.WaitTask(context.Background(), handle.Config.ID) 86 require.NoError(t, err) 87 result := <-ch 88 require.Nil(t, result.Err) 89 90 require.Zero(t, result.ExitCode) 91 92 // Get the stdout of the process and assert that it's not empty 93 stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "demo-app.stdout.0")) 94 require.NoError(t, err) 95 require.Contains(t, string(stdout), "Hello") 96 97 require.NoError(t, harness.DestroyTask(task.ID, true)) 98 } 99 100 func TestJavaDriver_Jar_Stop_Wait(t *testing.T) { 101 ci.Parallel(t) 102 javaCompatible(t) 103 104 ctx, cancel := context.WithCancel(context.Background()) 105 defer cancel() 106 107 d := NewDriver(ctx, testlog.HCLogger(t)) 108 harness := dtestutil.NewDriverHarness(t, d) 109 110 tc := &TaskConfig{ 111 JarPath: "demoapp.jar", 112 Args: []string{"600"}, 113 JvmOpts: []string{"-Xmx64m", "-Xms32m"}, 114 } 115 task := basicTask(t, "demo-app", tc) 116 117 cleanup := harness.MkAllocDir(task, true) 118 defer cleanup() 119 120 copyFile("./test-resources/demoapp.jar", filepath.Join(task.TaskDir().Dir, "demoapp.jar"), t) 121 122 handle, _, err := harness.StartTask(task) 123 require.NoError(t, err) 124 125 ch, err := harness.WaitTask(context.Background(), handle.Config.ID) 126 require.NoError(t, err) 127 128 require.NoError(t, harness.WaitUntilStarted(task.ID, 1*time.Second)) 129 130 go func() { 131 time.Sleep(10 * time.Millisecond) 132 harness.StopTask(task.ID, 2*time.Second, "SIGINT") 133 }() 134 135 select { 136 case result := <-ch: 137 require.False(t, result.Successful()) 138 case <-time.After(10 * time.Second): 139 require.Fail(t, "timeout waiting for task to shutdown") 140 } 141 142 // Ensure that the task is marked as dead, but account 143 // for WaitTask() closing channel before internal state is updated 144 testutil.WaitForResult(func() (bool, error) { 145 status, err := harness.InspectTask(task.ID) 146 if err != nil { 147 return false, fmt.Errorf("inspecting task failed: %v", err) 148 } 149 if status.State != drivers.TaskStateExited { 150 return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State) 151 } 152 153 return true, nil 154 }, func(err error) { 155 require.NoError(t, err) 156 }) 157 158 require.NoError(t, harness.DestroyTask(task.ID, true)) 159 } 160 161 func TestJavaDriver_Class_Start_Wait(t *testing.T) { 162 ci.Parallel(t) 163 javaCompatible(t) 164 165 ctx, cancel := context.WithCancel(context.Background()) 166 defer cancel() 167 168 d := NewDriver(ctx, testlog.HCLogger(t)) 169 harness := dtestutil.NewDriverHarness(t, d) 170 171 tc := &TaskConfig{ 172 Class: "Hello", 173 Args: []string{"1"}, 174 } 175 task := basicTask(t, "demo-app", tc) 176 177 cleanup := harness.MkAllocDir(task, true) 178 defer cleanup() 179 180 copyFile("./test-resources/Hello.class", filepath.Join(task.TaskDir().Dir, "Hello.class"), t) 181 182 handle, _, err := harness.StartTask(task) 183 require.NoError(t, err) 184 185 ch, err := harness.WaitTask(context.Background(), handle.Config.ID) 186 require.NoError(t, err) 187 result := <-ch 188 require.Nil(t, result.Err) 189 190 require.Zero(t, result.ExitCode) 191 192 // Get the stdout of the process and assert that it's not empty 193 stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "demo-app.stdout.0")) 194 require.NoError(t, err) 195 require.Contains(t, string(stdout), "Hello") 196 197 require.NoError(t, harness.DestroyTask(task.ID, true)) 198 } 199 200 func TestJavaCmdArgs(t *testing.T) { 201 ci.Parallel(t) 202 203 cases := []struct { 204 name string 205 cfg TaskConfig 206 expected []string 207 }{ 208 { 209 "jar_path_full", 210 TaskConfig{ 211 JvmOpts: []string{"-Xmx512m", "-Xms128m"}, 212 JarPath: "/jar-path.jar", 213 Args: []string{"hello", "world"}, 214 }, 215 []string{"-Xmx512m", "-Xms128m", "-jar", "/jar-path.jar", "hello", "world"}, 216 }, 217 { 218 "class_full", 219 TaskConfig{ 220 JvmOpts: []string{"-Xmx512m", "-Xms128m"}, 221 Class: "ClassName", 222 ClassPath: "/classpath", 223 Args: []string{"hello", "world"}, 224 }, 225 []string{"-Xmx512m", "-Xms128m", "-cp", "/classpath", "ClassName", "hello", "world"}, 226 }, 227 { 228 "jar_path_slim", 229 TaskConfig{ 230 JarPath: "/jar-path.jar", 231 }, 232 []string{"-jar", "/jar-path.jar"}, 233 }, 234 { 235 "class_slim", 236 TaskConfig{ 237 Class: "ClassName", 238 }, 239 []string{"ClassName"}, 240 }, 241 } 242 243 for _, c := range cases { 244 t.Run(c.name, func(t *testing.T) { 245 found := javaCmdArgs(c.cfg) 246 require.Equal(t, c.expected, found) 247 }) 248 } 249 } 250 251 func TestJavaDriver_ExecTaskStreaming(t *testing.T) { 252 ci.Parallel(t) 253 javaCompatible(t) 254 255 ctx, cancel := context.WithCancel(context.Background()) 256 defer cancel() 257 258 d := NewDriver(ctx, testlog.HCLogger(t)) 259 harness := dtestutil.NewDriverHarness(t, d) 260 defer harness.Kill() 261 262 tc := &TaskConfig{ 263 Class: "Hello", 264 Args: []string{"900"}, 265 } 266 task := basicTask(t, "demo-app", tc) 267 268 cleanup := harness.MkAllocDir(task, true) 269 defer cleanup() 270 271 copyFile("./test-resources/Hello.class", filepath.Join(task.TaskDir().Dir, "Hello.class"), t) 272 273 _, _, err := harness.StartTask(task) 274 require.NoError(t, err) 275 defer d.DestroyTask(task.ID, true) 276 277 dtestutil.ExecTaskStreamingConformanceTests(t, harness, task.ID) 278 279 } 280 func basicTask(t *testing.T, name string, taskConfig *TaskConfig) *drivers.TaskConfig { 281 t.Helper() 282 283 allocID := uuid.Generate() 284 task := &drivers.TaskConfig{ 285 AllocID: allocID, 286 ID: uuid.Generate(), 287 Name: name, 288 Resources: &drivers.Resources{ 289 NomadResources: &structs.AllocatedTaskResources{ 290 Memory: structs.AllocatedMemoryResources{ 291 MemoryMB: 128, 292 }, 293 Cpu: structs.AllocatedCpuResources{ 294 CpuShares: 100, 295 }, 296 }, 297 LinuxResources: &drivers.LinuxResources{ 298 MemoryLimitBytes: 134217728, 299 CPUShares: 100, 300 }, 301 }, 302 } 303 304 if cgutil.UseV2 { 305 task.Resources.LinuxResources.CpusetCgroupPath = filepath.Join(cgutil.CgroupRoot, "testing.slice", cgutil.CgroupScope(allocID, name)) 306 } 307 308 require.NoError(t, task.EncodeConcreteDriverConfig(&taskConfig)) 309 return task 310 } 311 312 // copyFile moves an existing file to the destination 313 func copyFile(src, dst string, t *testing.T) { 314 in, err := os.Open(src) 315 if err != nil { 316 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 317 } 318 defer in.Close() 319 out, err := os.Create(dst) 320 if err != nil { 321 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 322 } 323 defer func() { 324 if err := out.Close(); err != nil { 325 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 326 } 327 }() 328 if _, err = io.Copy(out, in); err != nil { 329 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 330 } 331 } 332 333 func TestConfig_ParseAllHCL(t *testing.T) { 334 ci.Parallel(t) 335 336 cfgStr := ` 337 config { 338 class = "java.main" 339 class_path = "/tmp/cp" 340 jar_path = "/tmp/jar.jar" 341 jvm_options = ["-Xmx600"] 342 args = ["arg1", "arg2"] 343 }` 344 345 expected := &TaskConfig{ 346 Class: "java.main", 347 ClassPath: "/tmp/cp", 348 JarPath: "/tmp/jar.jar", 349 JvmOpts: []string{"-Xmx600"}, 350 Args: []string{"arg1", "arg2"}, 351 } 352 353 var tc *TaskConfig 354 hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc) 355 356 require.EqualValues(t, expected, tc) 357 } 358 359 // Tests that a given DNSConfig properly configures dns 360 func Test_dnsConfig(t *testing.T) { 361 ci.Parallel(t) 362 ctestutil.RequireRoot(t) 363 javaCompatible(t) 364 require := require.New(t) 365 ctx, cancel := context.WithCancel(context.Background()) 366 defer cancel() 367 368 d := NewDriver(ctx, testlog.HCLogger(t)) 369 harness := dtestutil.NewDriverHarness(t, d) 370 defer harness.Kill() 371 372 cases := []struct { 373 name string 374 cfg *drivers.DNSConfig 375 }{ 376 { 377 name: "nil DNSConfig", 378 }, 379 { 380 name: "basic", 381 cfg: &drivers.DNSConfig{ 382 Servers: []string{"1.1.1.1", "1.0.0.1"}, 383 }, 384 }, 385 { 386 name: "full", 387 cfg: &drivers.DNSConfig{ 388 Servers: []string{"1.1.1.1", "1.0.0.1"}, 389 Searches: []string{"local.test", "node.consul"}, 390 Options: []string{"ndots:2", "edns0"}, 391 }, 392 }, 393 } 394 395 for _, c := range cases { 396 tc := &TaskConfig{ 397 Class: "Hello", 398 Args: []string{"900"}, 399 } 400 task := basicTask(t, "demo-app", tc) 401 task.DNS = c.cfg 402 403 cleanup := harness.MkAllocDir(task, false) 404 defer cleanup() 405 406 _, _, err := harness.StartTask(task) 407 require.NoError(err) 408 defer d.DestroyTask(task.ID, true) 409 410 dtestutil.TestTaskDNSConfig(t, harness, task.ID, c.cfg) 411 } 412 413 } 414 415 func TestDriver_Config_validate(t *testing.T) { 416 ci.Parallel(t) 417 418 t.Run("pid/ipc", func(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 }) 436 437 t.Run("allow_caps", func(t *testing.T) { 438 for _, tc := range []struct { 439 ac []string 440 exp error 441 }{ 442 {ac: []string{}, exp: nil}, 443 {ac: []string{"all"}, exp: nil}, 444 {ac: []string{"chown", "sys_time"}, exp: nil}, 445 {ac: []string{"CAP_CHOWN", "cap_sys_time"}, exp: nil}, 446 {ac: []string{"chown", "not_valid", "sys_time"}, exp: errors.New("allow_caps configured with capabilities not supported by system: not_valid")}, 447 } { 448 require.Equal(t, tc.exp, (&Config{ 449 DefaultModePID: "private", 450 DefaultModeIPC: "private", 451 AllowCaps: tc.ac, 452 }).validate()) 453 } 454 }) 455 } 456 457 func TestDriver_TaskConfig_validate(t *testing.T) { 458 ci.Parallel(t) 459 460 t.Run("pid/ipc", func(t *testing.T) { 461 for _, tc := range []struct { 462 pidMode, ipcMode string 463 exp error 464 }{ 465 {pidMode: "host", ipcMode: "host", exp: nil}, 466 {pidMode: "host", ipcMode: "private", exp: nil}, 467 {pidMode: "host", ipcMode: "", exp: nil}, 468 {pidMode: "host", ipcMode: "other", exp: errors.New(`ipc_mode must be "private" or "host", got "other"`)}, 469 470 {pidMode: "host", ipcMode: "host", exp: nil}, 471 {pidMode: "private", ipcMode: "host", exp: nil}, 472 {pidMode: "", ipcMode: "host", exp: nil}, 473 {pidMode: "other", ipcMode: "host", exp: errors.New(`pid_mode must be "private" or "host", got "other"`)}, 474 } { 475 require.Equal(t, tc.exp, (&TaskConfig{ 476 ModePID: tc.pidMode, 477 ModeIPC: tc.ipcMode, 478 }).validate()) 479 } 480 }) 481 482 t.Run("cap_add", func(t *testing.T) { 483 for _, tc := range []struct { 484 adds []string 485 exp error 486 }{ 487 {adds: nil, exp: nil}, 488 {adds: []string{"chown"}, exp: nil}, 489 {adds: []string{"CAP_CHOWN"}, exp: nil}, 490 {adds: []string{"chown", "sys_time"}, exp: nil}, 491 {adds: []string{"chown", "not_valid", "sys_time"}, exp: errors.New("cap_add configured with capabilities not supported by system: not_valid")}, 492 } { 493 require.Equal(t, tc.exp, (&TaskConfig{ 494 CapAdd: tc.adds, 495 }).validate()) 496 } 497 }) 498 499 t.Run("cap_drop", func(t *testing.T) { 500 for _, tc := range []struct { 501 drops []string 502 exp error 503 }{ 504 {drops: nil, exp: nil}, 505 {drops: []string{"chown"}, exp: nil}, 506 {drops: []string{"CAP_CHOWN"}, exp: nil}, 507 {drops: []string{"chown", "sys_time"}, exp: nil}, 508 {drops: []string{"chown", "not_valid", "sys_time"}, exp: errors.New("cap_drop configured with capabilities not supported by system: not_valid")}, 509 } { 510 require.Equal(t, tc.exp, (&TaskConfig{ 511 CapDrop: tc.drops, 512 }).validate()) 513 } 514 }) 515 }