github.com/ilhicas/nomad@v0.11.8/drivers/qemu/driver_test.go (about) 1 package qemu 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 "time" 11 12 ctestutil "github.com/hashicorp/nomad/client/testutil" 13 "github.com/hashicorp/nomad/helper/pluginutils/hclutils" 14 "github.com/hashicorp/nomad/helper/testlog" 15 "github.com/hashicorp/nomad/helper/uuid" 16 "github.com/hashicorp/nomad/nomad/structs" 17 "github.com/hashicorp/nomad/plugins/drivers" 18 dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" 19 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 20 "github.com/hashicorp/nomad/testutil" 21 "github.com/stretchr/testify/require" 22 ) 23 24 // TODO(preetha) - tests remaining 25 // using monitor socket for graceful shutdown 26 27 // Verifies starting a qemu image and stopping it 28 func TestQemuDriver_Start_Wait_Stop(t *testing.T) { 29 ctestutil.QemuCompatible(t) 30 if !testutil.IsCI() { 31 t.Parallel() 32 } 33 34 require := require.New(t) 35 ctx, cancel := context.WithCancel(context.Background()) 36 defer cancel() 37 38 d := NewQemuDriver(ctx, testlog.HCLogger(t)) 39 harness := dtestutil.NewDriverHarness(t, d) 40 41 task := &drivers.TaskConfig{ 42 ID: uuid.Generate(), 43 Name: "linux", 44 Resources: &drivers.Resources{ 45 NomadResources: &structs.AllocatedTaskResources{ 46 Memory: structs.AllocatedMemoryResources{ 47 MemoryMB: 512, 48 }, 49 Cpu: structs.AllocatedCpuResources{ 50 CpuShares: 100, 51 }, 52 Networks: []*structs.NetworkResource{ 53 { 54 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 55 }, 56 }, 57 }, 58 }, 59 } 60 61 tc := &TaskConfig{ 62 ImagePath: "linux-0.2.img", 63 Accelerator: "tcg", 64 GracefulShutdown: false, 65 PortMap: map[string]int{ 66 "main": 22, 67 "web": 8080, 68 }, 69 Args: []string{"-nodefconfig", "-nodefaults"}, 70 } 71 require.NoError(task.EncodeConcreteDriverConfig(&tc)) 72 cleanup := harness.MkAllocDir(task, true) 73 defer cleanup() 74 75 taskDir := filepath.Join(task.AllocDir, task.Name) 76 77 copyFile("./test-resources/linux-0.2.img", filepath.Join(taskDir, "linux-0.2.img"), t) 78 79 handle, _, err := harness.StartTask(task) 80 require.NoError(err) 81 82 require.NotNil(handle) 83 84 // Ensure that sending a Signal returns an error 85 err = d.SignalTask(task.ID, "SIGINT") 86 require.NotNil(err) 87 88 require.NoError(harness.DestroyTask(task.ID, true)) 89 90 } 91 92 // Verifies monitor socket path for old qemu 93 func TestQemuDriver_GetMonitorPathOldQemu(t *testing.T) { 94 ctestutil.QemuCompatible(t) 95 if !testutil.IsCI() { 96 t.Parallel() 97 } 98 99 require := require.New(t) 100 ctx, cancel := context.WithCancel(context.Background()) 101 defer cancel() 102 103 d := NewQemuDriver(ctx, testlog.HCLogger(t)) 104 harness := dtestutil.NewDriverHarness(t, d) 105 106 task := &drivers.TaskConfig{ 107 ID: uuid.Generate(), 108 Name: "linux", 109 Resources: &drivers.Resources{ 110 NomadResources: &structs.AllocatedTaskResources{ 111 Memory: structs.AllocatedMemoryResources{ 112 MemoryMB: 512, 113 }, 114 Cpu: structs.AllocatedCpuResources{ 115 CpuShares: 100, 116 }, 117 Networks: []*structs.NetworkResource{ 118 { 119 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 120 }, 121 }, 122 }, 123 }, 124 } 125 126 cleanup := harness.MkAllocDir(task, true) 127 defer cleanup() 128 129 fingerPrint := &drivers.Fingerprint{ 130 Attributes: map[string]*pstructs.Attribute{ 131 driverVersionAttr: pstructs.NewStringAttribute("2.0.0"), 132 }, 133 } 134 shortPath := strings.Repeat("x", 10) 135 qemuDriver := d.(*Driver) 136 _, err := qemuDriver.getMonitorPath(shortPath, fingerPrint) 137 require.Nil(err) 138 139 longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100) 140 _, err = qemuDriver.getMonitorPath(longPath, fingerPrint) 141 require.NotNil(err) 142 143 // Max length includes the '/' separator and socket name 144 maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1 145 maxLengthLegacyPath := strings.Repeat("x", maxLengthCount) 146 _, err = qemuDriver.getMonitorPath(maxLengthLegacyPath, fingerPrint) 147 require.Nil(err) 148 } 149 150 // Verifies monitor socket path for new qemu version 151 func TestQemuDriver_GetMonitorPathNewQemu(t *testing.T) { 152 ctestutil.QemuCompatible(t) 153 if !testutil.IsCI() { 154 t.Parallel() 155 } 156 157 require := require.New(t) 158 ctx, cancel := context.WithCancel(context.Background()) 159 defer cancel() 160 161 d := NewQemuDriver(ctx, testlog.HCLogger(t)) 162 harness := dtestutil.NewDriverHarness(t, d) 163 164 task := &drivers.TaskConfig{ 165 ID: uuid.Generate(), 166 Name: "linux", 167 Resources: &drivers.Resources{ 168 NomadResources: &structs.AllocatedTaskResources{ 169 Memory: structs.AllocatedMemoryResources{ 170 MemoryMB: 512, 171 }, 172 Cpu: structs.AllocatedCpuResources{ 173 CpuShares: 100, 174 }, 175 Networks: []*structs.NetworkResource{ 176 { 177 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 178 }, 179 }, 180 }, 181 }, 182 } 183 184 cleanup := harness.MkAllocDir(task, true) 185 defer cleanup() 186 187 fingerPrint := &drivers.Fingerprint{ 188 Attributes: map[string]*pstructs.Attribute{ 189 driverVersionAttr: pstructs.NewStringAttribute("2.99.99"), 190 }, 191 } 192 shortPath := strings.Repeat("x", 10) 193 qemuDriver := d.(*Driver) 194 _, err := qemuDriver.getMonitorPath(shortPath, fingerPrint) 195 require.Nil(err) 196 197 // Should not return an error in this qemu version 198 longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100) 199 _, err = qemuDriver.getMonitorPath(longPath, fingerPrint) 200 require.Nil(err) 201 202 // Max length includes the '/' separator and socket name 203 maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1 204 maxLengthLegacyPath := strings.Repeat("x", maxLengthCount) 205 _, err = qemuDriver.getMonitorPath(maxLengthLegacyPath, fingerPrint) 206 require.Nil(err) 207 } 208 209 // copyFile moves an existing file to the destination 210 func copyFile(src, dst string, t *testing.T) { 211 in, err := os.Open(src) 212 if err != nil { 213 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 214 } 215 defer in.Close() 216 out, err := os.Create(dst) 217 if err != nil { 218 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 219 } 220 defer func() { 221 if err := out.Close(); err != nil { 222 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 223 } 224 }() 225 if _, err = io.Copy(out, in); err != nil { 226 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 227 } 228 if err := out.Sync(); err != nil { 229 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 230 } 231 } 232 233 // Verifies starting a qemu image and stopping it 234 func TestQemuDriver_User(t *testing.T) { 235 ctestutil.QemuCompatible(t) 236 if !testutil.IsCI() { 237 t.Parallel() 238 } 239 240 require := require.New(t) 241 ctx, cancel := context.WithCancel(context.Background()) 242 defer cancel() 243 244 d := NewQemuDriver(ctx, testlog.HCLogger(t)) 245 harness := dtestutil.NewDriverHarness(t, d) 246 247 task := &drivers.TaskConfig{ 248 ID: uuid.Generate(), 249 Name: "linux", 250 User: "alice", 251 Resources: &drivers.Resources{ 252 NomadResources: &structs.AllocatedTaskResources{ 253 Memory: structs.AllocatedMemoryResources{ 254 MemoryMB: 512, 255 }, 256 Cpu: structs.AllocatedCpuResources{ 257 CpuShares: 100, 258 }, 259 Networks: []*structs.NetworkResource{ 260 { 261 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 262 }, 263 }, 264 }, 265 }, 266 } 267 268 tc := &TaskConfig{ 269 ImagePath: "linux-0.2.img", 270 Accelerator: "tcg", 271 GracefulShutdown: false, 272 PortMap: map[string]int{ 273 "main": 22, 274 "web": 8080, 275 }, 276 Args: []string{"-nodefconfig", "-nodefaults"}, 277 } 278 require.NoError(task.EncodeConcreteDriverConfig(&tc)) 279 cleanup := harness.MkAllocDir(task, true) 280 defer cleanup() 281 282 taskDir := filepath.Join(task.AllocDir, task.Name) 283 284 copyFile("./test-resources/linux-0.2.img", filepath.Join(taskDir, "linux-0.2.img"), t) 285 286 _, _, err := harness.StartTask(task) 287 require.Error(err) 288 require.Contains(err.Error(), "unknown user alice", err.Error()) 289 290 } 291 292 // Verifies getting resource usage stats 293 // TODO(preetha) this test needs random sleeps to pass 294 func TestQemuDriver_Stats(t *testing.T) { 295 ctestutil.QemuCompatible(t) 296 if !testutil.IsCI() { 297 t.Parallel() 298 } 299 300 require := require.New(t) 301 ctx, cancel := context.WithCancel(context.Background()) 302 defer cancel() 303 304 d := NewQemuDriver(ctx, testlog.HCLogger(t)) 305 harness := dtestutil.NewDriverHarness(t, d) 306 307 task := &drivers.TaskConfig{ 308 ID: uuid.Generate(), 309 Name: "linux", 310 Resources: &drivers.Resources{ 311 NomadResources: &structs.AllocatedTaskResources{ 312 Memory: structs.AllocatedMemoryResources{ 313 MemoryMB: 512, 314 }, 315 Cpu: structs.AllocatedCpuResources{ 316 CpuShares: 100, 317 }, 318 Networks: []*structs.NetworkResource{ 319 { 320 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 321 }, 322 }, 323 }, 324 }, 325 } 326 327 tc := &TaskConfig{ 328 ImagePath: "linux-0.2.img", 329 Accelerator: "tcg", 330 GracefulShutdown: false, 331 PortMap: map[string]int{ 332 "main": 22, 333 "web": 8080, 334 }, 335 Args: []string{"-nodefconfig", "-nodefaults"}, 336 } 337 require.NoError(task.EncodeConcreteDriverConfig(&tc)) 338 cleanup := harness.MkAllocDir(task, true) 339 defer cleanup() 340 341 taskDir := filepath.Join(task.AllocDir, task.Name) 342 343 copyFile("./test-resources/linux-0.2.img", filepath.Join(taskDir, "linux-0.2.img"), t) 344 345 handle, _, err := harness.StartTask(task) 346 require.NoError(err) 347 348 require.NotNil(handle) 349 350 // Wait for task to start 351 _, err = harness.WaitTask(context.Background(), handle.Config.ID) 352 require.NoError(err) 353 354 // Wait until task started 355 require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second)) 356 time.Sleep(30 * time.Second) 357 statsCtx, cancel := context.WithCancel(context.Background()) 358 defer cancel() 359 statsCh, err := harness.TaskStats(statsCtx, task.ID, time.Second*10) 360 require.NoError(err) 361 362 select { 363 case stats := <-statsCh: 364 t.Logf("CPU:%+v Memory:%+v\n", stats.ResourceUsage.CpuStats, stats.ResourceUsage.MemoryStats) 365 require.NotZero(stats.ResourceUsage.MemoryStats.RSS) 366 require.NoError(harness.DestroyTask(task.ID, true)) 367 case <-time.After(time.Second * 1): 368 require.Fail("timeout receiving from stats") 369 } 370 371 } 372 373 func TestQemuDriver_Fingerprint(t *testing.T) { 374 require := require.New(t) 375 376 ctestutil.QemuCompatible(t) 377 if !testutil.IsCI() { 378 t.Parallel() 379 } 380 381 ctx, cancel := context.WithCancel(context.Background()) 382 defer cancel() 383 384 d := NewQemuDriver(ctx, testlog.HCLogger(t)) 385 harness := dtestutil.NewDriverHarness(t, d) 386 387 fingerCh, err := harness.Fingerprint(context.Background()) 388 require.NoError(err) 389 select { 390 case finger := <-fingerCh: 391 require.Equal(drivers.HealthStateHealthy, finger.Health) 392 require.True(finger.Attributes["driver.qemu"].GetBool()) 393 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 394 require.Fail("timeout receiving fingerprint") 395 } 396 } 397 398 func TestConfig_ParseAllHCL(t *testing.T) { 399 cfgStr := ` 400 config { 401 image_path = "/tmp/image_path" 402 accelerator = "kvm" 403 args = ["arg1", "arg2"] 404 port_map { 405 http = 80 406 https = 443 407 } 408 graceful_shutdown = true 409 }` 410 411 expected := &TaskConfig{ 412 ImagePath: "/tmp/image_path", 413 Accelerator: "kvm", 414 Args: []string{"arg1", "arg2"}, 415 PortMap: map[string]int{ 416 "http": 80, 417 "https": 443, 418 }, 419 GracefulShutdown: true, 420 } 421 422 var tc *TaskConfig 423 hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc) 424 425 require.EqualValues(t, expected, tc) 426 }