44 "bytes"
55 "context"
66 "os"
7+ "path/filepath"
8+ "strings"
79 "testing"
810 "time"
911
@@ -21,19 +23,21 @@ import (
2123 "github.com/stretchr/testify/require"
2224)
2325
24- // TestVGPU is an integration test that verifies vGPU (SR-IOV mdev) support works.
26+ // TestVGPU is an integration test that verifies vGPU (SR-IOV) support works
27+ // on the host's framework: mdev or NVIDIA's vendor-specific VFIO.
2528//
2629// This test automatically detects vGPU availability and skips if:
27- // - No SR-IOV VFs are found in /sys/class/mdev_bus/
30+ // - No vGPU framework (mdev or vendor VFIO) is discovered
2831// - No vGPU profiles are available
29- // - Not running as root (required for mdev creation )
32+ // - Not running as root (required for sysfs vGPU assignment )
3033// - KVM is not available
3134//
3235// To run manually:
3336//
3437// sudo go test -v -run TestVGPU -timeout 5m ./integration/...
3538//
36- // Note: This test verifies mdev creation and PCI device visibility inside the VM.
39+ // Note: This test verifies vGPU assignment, release on stop, reacquisition on
40+ // start, and PCI device visibility inside the VM.
3741// It does NOT test nvidia-smi or CUDA functionality since that requires NVIDIA
3842// guest drivers pre-installed in the image.
3943func TestVGPU (t * testing.T ) {
@@ -159,9 +163,18 @@ func TestVGPU(t *testing.T) {
159163 instanceID = inst .Id
160164 t .Logf ("Instance created: %s" , inst .Id )
161165
162- // Verify mdev UUID was assigned
163- require .NotEmpty (t , inst .GPUMdevUUID , "Instance should have mdev UUID assigned" )
164- t .Logf ("mdev UUID: %s" , inst .GPUMdevUUID )
166+ // Verify the assignment matches the host's framework
167+ require .NotEmpty (t , inst .GPUDevicePath , "Instance should have a vGPU device path assigned" )
168+ switch inst .GPUFramework {
169+ case devices .VGPUFrameworkMdev :
170+ require .NotEmpty (t , inst .GPUMdevUUID , "mdev instance should have a UUID assigned" )
171+ t .Logf ("mdev UUID: %s" , inst .GPUMdevUUID )
172+ case devices .VGPUFrameworkVendorVFIO :
173+ require .Empty (t , inst .GPUMdevUUID , "vendor VFIO instance should not have an mdev UUID" )
174+ t .Logf ("vendor VFIO VF: %s" , inst .GPUDevicePath )
175+ default :
176+ t .Fatalf ("unexpected vGPU framework %q" , inst .GPUFramework )
177+ }
165178
166179 // Step 5: Check GPU resources AFTER creating instance
167180 t .Run ("ResourcesDecrementedAfterCreation" , func (t * testing.T ) {
@@ -180,12 +193,9 @@ func TestVGPU(t *testing.T) {
180193 assert .Less (t , availableAfter , availableBefore , "available instances should decrease after creating VM" )
181194 })
182195
183- // Step 6: Verify mdev was created in sysfs
184- t .Run ("MdevCreated" , func (t * testing.T ) {
185- mdevPath := "/sys/bus/mdev/devices/" + inst .GPUMdevUUID
186- _ , err := os .Stat (mdevPath )
187- assert .NoError (t , err , "mdev device should exist at %s" , mdevPath )
188- t .Logf ("mdev exists at: %s" , mdevPath )
196+ // Step 6: Verify the assignment exists in sysfs
197+ t .Run ("VGPUAssignedInSysfs" , func (t * testing.T ) {
198+ assertVGPUAssigned (t , inst .GPUFramework , inst .GPUDevicePath )
189199 })
190200
191201 // Step 7: Wait for guest agent to be ready
@@ -225,13 +235,74 @@ func TestVGPU(t *testing.T) {
225235 require .NoError (t , err )
226236
227237 assert .Equal (t , profile , actualInst .GPUProfile , "GPU profile should match" )
228- assert .NotEmpty (t , actualInst .GPUMdevUUID , "mdev UUID should be set" )
229- t .Logf ("Instance GPU: profile=%s, mdev=%s" , actualInst .GPUProfile , actualInst .GPUMdevUUID )
238+ assert .Equal (t , inst .GPUFramework , actualInst .GPUFramework , "framework should match" )
239+ assert .NotEmpty (t , actualInst .GPUDevicePath , "device path should be set" )
240+ if inst .GPUFramework == devices .VGPUFrameworkMdev {
241+ assert .NotEmpty (t , actualInst .GPUMdevUUID , "mdev UUID should be set" )
242+ }
243+ t .Logf ("Instance GPU: profile=%s, framework=%s, device=%s" , actualInst .GPUProfile , actualInst .GPUFramework , actualInst .GPUDevicePath )
244+ })
245+
246+ // Step 10: Stop releases the assignment
247+ t .Log ("Step 10: Stopping instance to release the vGPU..." )
248+ _ , err = instanceManager .StopInstance (ctx , inst .Id )
249+ require .NoError (t , err , "stop should succeed" )
250+
251+ t .Run ("VGPUReleasedOnStop" , func (t * testing.T ) {
252+ stopped , err := instanceManager .GetInstance (ctx , inst .Id )
253+ require .NoError (t , err )
254+ assert .Empty (t , stopped .GPUDevicePath , "assignment metadata should be cleared on stop" )
255+ assertVGPUReleased (t , inst .GPUFramework , inst .GPUDevicePath )
256+ })
257+
258+ // Step 11: Start reacquires an assignment
259+ t .Log ("Step 11: Starting instance to reacquire a vGPU..." )
260+ started , err := instanceManager .StartInstance (ctx , inst .Id , instances.StartInstanceRequest {})
261+ require .NoError (t , err , "start should succeed" )
262+
263+ t .Run ("VGPUReacquiredOnStart" , func (t * testing.T ) {
264+ require .NotEmpty (t , started .GPUDevicePath , "start should assign a vGPU" )
265+ assert .Equal (t , inst .GPUFramework , started .GPUFramework , "framework should match" )
266+ assertVGPUAssigned (t , started .GPUFramework , started .GPUDevicePath )
230267 })
231268
232269 t .Log ("✅ vGPU test PASSED!" )
233270}
234271
272+ // assertVGPUAssigned verifies in sysfs that the device at path carries a live
273+ // vGPU assignment for the given framework.
274+ func assertVGPUAssigned (t * testing.T , framework devices.VGPUFramework , devicePath string ) {
275+ t .Helper ()
276+ switch framework {
277+ case devices .VGPUFrameworkMdev :
278+ _ , err := os .Stat (devicePath )
279+ assert .NoError (t , err , "mdev device should exist at %s" , devicePath )
280+ case devices .VGPUFrameworkVendorVFIO :
281+ data , err := os .ReadFile (filepath .Join (devicePath , "nvidia" , "current_vgpu_type" ))
282+ require .NoError (t , err , "VF should expose current_vgpu_type" )
283+ assert .NotEqual (t , "0" , strings .TrimSpace (string (data )), "VF should have a vGPU type assigned" )
284+ default :
285+ t .Fatalf ("unexpected vGPU framework %q" , framework )
286+ }
287+ }
288+
289+ // assertVGPUReleased verifies in sysfs that the device at path no longer
290+ // carries a vGPU assignment.
291+ func assertVGPUReleased (t * testing.T , framework devices.VGPUFramework , devicePath string ) {
292+ t .Helper ()
293+ switch framework {
294+ case devices .VGPUFrameworkMdev :
295+ _ , err := os .Stat (devicePath )
296+ assert .True (t , os .IsNotExist (err ), "mdev device should be gone from %s" , devicePath )
297+ case devices .VGPUFrameworkVendorVFIO :
298+ data , err := os .ReadFile (filepath .Join (devicePath , "nvidia" , "current_vgpu_type" ))
299+ require .NoError (t , err , "VF should expose current_vgpu_type" )
300+ assert .Equal (t , "0" , strings .TrimSpace (string (data )), "VF assignment should be released" )
301+ default :
302+ t .Fatalf ("unexpected vGPU framework %q" , framework )
303+ }
304+ }
305+
235306// checkVGPUTestPrerequisites checks if vGPU test can run.
236307// Returns (skipReason, profileName) - skipReason is empty if all prerequisites are met.
237308func checkVGPUTestPrerequisites () (string , string ) {
0 commit comments