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