Skip to content

Commit d489510

Browse files
committed
Keep stop's no-op contract on failed retained vGPU release and pass assignments to DestroyVGPU as a struct
1 parent 746532c commit d489510

8 files changed

Lines changed: 55 additions & 27 deletions

File tree

lib/devices/mdev_darwin.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ func IsMdevInUse(mdevUUID string) bool {
5555
return false
5656
}
5757

58-
func DestroyVGPU(ctx context.Context, framework VGPUFramework, devicePath, mdevUUID string) error {
59-
if framework != VGPUFrameworkNone && framework != VGPUFrameworkMdev {
60-
return fmt.Errorf("unknown vGPU framework %q", framework)
58+
func DestroyVGPU(ctx context.Context, assignment VGPUAssignment) error {
59+
if assignment.Framework != VGPUFrameworkNone && assignment.Framework != VGPUFrameworkMdev {
60+
return fmt.Errorf("unknown vGPU framework %q", assignment.Framework)
6161
}
6262
return nil
6363
}

lib/devices/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ type VirtualFunction struct {
8181
Allocated bool `json:"allocated"` // true if a vGPU is assigned to this VF
8282
}
8383

84+
// VGPUAssignment identifies an existing vGPU assignment to release.
85+
type VGPUAssignment struct {
86+
Framework VGPUFramework
87+
DevicePath string
88+
MdevUUID string
89+
}
90+
8491
type VGPUDevice struct {
8592
Framework VGPUFramework
8693
VFAddress string

lib/devices/vgpu_linux.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ func CreateVGPU(ctx context.Context, profileName, instanceID string) (*VGPUDevic
2323
}, nil
2424
}
2525

26-
func DestroyVGPU(ctx context.Context, framework VGPUFramework, devicePath, mdevUUID string) error {
27-
if framework != VGPUFrameworkNone && framework != VGPUFrameworkMdev {
28-
return fmt.Errorf("unknown vGPU framework %q", framework)
26+
func DestroyVGPU(ctx context.Context, assignment VGPUAssignment) error {
27+
if assignment.Framework != VGPUFrameworkNone && assignment.Framework != VGPUFrameworkMdev {
28+
return fmt.Errorf("unknown vGPU framework %q", assignment.Framework)
2929
}
30+
mdevUUID := assignment.MdevUUID
3031
if mdevUUID == "" {
31-
if devicePath == "" {
32+
if assignment.DevicePath == "" {
3233
return nil
3334
}
34-
mdevUUID = filepath.Base(devicePath)
35+
mdevUUID = filepath.Base(assignment.DevicePath)
3536
}
3637
return DestroyMdev(ctx, mdevUUID)
3738
}

lib/instances/create.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,12 @@ func (m *manager) createInstance(
287287

288288
// Add vGPU cleanup to stack
289289
cu.Add(func() {
290-
if err := devices.DestroyVGPU(ctx, gpuDevice.Framework, gpuDevice.SysfsPath, gpuDevice.MdevUUID); err != nil {
290+
assignment := devices.VGPUAssignment{
291+
Framework: gpuDevice.Framework,
292+
DevicePath: gpuDevice.SysfsPath,
293+
MdevUUID: gpuDevice.MdevUUID,
294+
}
295+
if err := devices.DestroyVGPU(ctx, assignment); err != nil {
291296
log.WarnContext(ctx, "failed to destroy vGPU on cleanup", "instance_id", id, "error", err)
292297
}
293298
})

lib/instances/lifecycle_noop_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func TestStopStoppedInstanceReleasesRetainedVGPU(t *testing.T) {
208208
assert.Empty(t, stored.GPUDevicePath)
209209
}
210210

211-
func TestStopStoppedInstanceVGPUReleaseFailureReturnsError(t *testing.T) {
211+
func TestStopStoppedInstanceVGPUReleaseFailureRemainsNoop(t *testing.T) {
212212
m, id := newLifecycleNoopManagerWithInstance(t, StateStopped, time.Now().UTC())
213213
meta, err := m.loadMetadata(id)
214214
require.NoError(t, err)
@@ -217,9 +217,10 @@ func TestStopStoppedInstanceVGPUReleaseFailureReturnsError(t *testing.T) {
217217
meta.GPUDevicePath = "/sys/bus/pci/devices/0000:82:00.4"
218218
require.NoError(t, m.saveMetadata(meta))
219219

220-
_, err = m.StopInstance(context.Background(), id)
221-
require.Error(t, err)
222-
assert.ErrorContains(t, err, "destroy vGPU")
220+
inst, err := m.StopInstance(context.Background(), id)
221+
require.NoError(t, err)
222+
require.NotNil(t, inst)
223+
assert.Equal(t, StateStopped, inst.State)
223224

224225
stored, err := m.loadMetadata(id)
225226
require.NoError(t, err)

lib/instances/manager.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,10 +623,10 @@ func (m *manager) StopInstance(ctx context.Context, id string) (*Instance, error
623623
}
624624
// A stopped instance can retain a vGPU assignment when the release
625625
// failed during the original stop. Retry it here so the vGPU slot is
626-
// not held until the next start, delete, or hypeman restart.
627-
if err := m.releaseRetainedVGPULocked(ctx, id); err != nil {
628-
return nil, err
629-
}
626+
// not held until the next start, delete, or hypeman restart. A failed
627+
// retry only logs, keeping stop's no-op contract for already-stopped
628+
// instances.
629+
m.releaseRetainedVGPULocked(ctx, id)
630630
updated, err := m.currentInstanceWithoutHydration(ctx, id)
631631
if err != nil {
632632
return nil, err

lib/instances/start.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,12 @@ func (m *manager) startInstance(
160160
setStoredVGPUDevice(stored, device)
161161
// Add vGPU cleanup to stack
162162
cu.Add(func() {
163-
if err := devices.DestroyVGPU(ctx, device.Framework, device.SysfsPath, device.MdevUUID); err != nil {
163+
assignment := devices.VGPUAssignment{
164+
Framework: device.Framework,
165+
DevicePath: device.SysfsPath,
166+
MdevUUID: device.MdevUUID,
167+
}
168+
if err := devices.DestroyVGPU(ctx, assignment); err != nil {
164169
log.WarnContext(ctx, "failed to destroy vGPU on cleanup", "instance_id", id, "error", err)
165170
}
166171
})

lib/instances/vgpu.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package instances
22

33
import (
44
"context"
5-
"fmt"
65
"path/filepath"
76

87
"github.com/kernel/hypeman/lib/devices"
@@ -24,7 +23,12 @@ func clearStoredVGPUDevice(stored *StoredMetadata) {
2423
func releaseStoredVGPU(ctx context.Context, stored *StoredMetadata) error {
2524
path := storedVGPUDevicePath(stored)
2625
if path != "" {
27-
if err := devices.DestroyVGPU(ctx, stored.GPUFramework, path, stored.GPUMdevUUID); err != nil {
26+
assignment := devices.VGPUAssignment{
27+
Framework: stored.GPUFramework,
28+
DevicePath: path,
29+
MdevUUID: stored.GPUMdevUUID,
30+
}
31+
if err := devices.DestroyVGPU(ctx, assignment); err != nil {
2832
return err
2933
}
3034
}
@@ -34,21 +38,26 @@ func releaseStoredVGPU(ctx context.Context, stored *StoredMetadata) error {
3438

3539
// releaseRetainedVGPULocked releases a vGPU assignment retained on a stopped
3640
// instance after a failed release during the original stop. It is a no-op
37-
// when no assignment is retained. The caller must hold the instance lock.
38-
func (m *manager) releaseRetainedVGPULocked(ctx context.Context, id string) error {
41+
// when no assignment is retained, and a failed retry only logs so the
42+
// metadata stays for the next retry. The caller must hold the instance lock.
43+
func (m *manager) releaseRetainedVGPULocked(ctx context.Context, id string) {
44+
log := logger.FromContext(ctx)
3945
meta, err := m.loadMetadata(id)
4046
if err != nil {
41-
return err
47+
log.WarnContext(ctx, "failed to load metadata for retained vGPU release", "instance_id", id, "error", err)
48+
return
4249
}
4350
stored := &meta.StoredMetadata
4451
if storedVGPUDevicePath(stored) == "" {
45-
return nil
52+
return
4653
}
4754
if err := releaseStoredVGPU(ctx, stored); err != nil {
48-
logger.FromContext(ctx).ErrorContext(ctx, "failed to destroy retained vGPU; retaining assignment metadata", "instance_id", id, "error", err)
49-
return fmt.Errorf("destroy vGPU: %w", err)
55+
log.WarnContext(ctx, "failed to destroy retained vGPU; retaining assignment metadata", "instance_id", id, "error", err)
56+
return
57+
}
58+
if err := m.saveMetadata(meta); err != nil {
59+
log.WarnContext(ctx, "failed to save metadata after retained vGPU release", "instance_id", id, "error", err)
5060
}
51-
return m.saveMetadata(meta)
5261
}
5362

5463
func storedVGPUDevicePath(stored *StoredMetadata) string {

0 commit comments

Comments
 (0)