Skip to content

Commit f09abba

Browse files
Justin Terryrawahars
authored andcommitted
Implements the VM VPMem Controller
This change implements the VPMem controller operations for the new V2 shim. Signed-off-by: Justin Terry <terryjustin@microsoft.com>
1 parent e13a818 commit f09abba

9 files changed

Lines changed: 1552 additions & 23 deletions

File tree

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//go:build windows
2+
3+
package vpmem
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"sync"
9+
10+
"github.com/Microsoft/hcsshim/internal/controller/device/vpmem/device"
11+
"github.com/Microsoft/hcsshim/internal/controller/device/vpmem/mount"
12+
13+
"github.com/google/uuid"
14+
)
15+
16+
type VMVPMemOps interface {
17+
device.VMVPMemAdder
18+
device.VMVPMemRemover
19+
}
20+
21+
type LinuxGuestVPMemOps interface {
22+
mount.LinuxGuestVPMemMounter
23+
mount.LinuxGuestVPMemUnmounter
24+
}
25+
26+
// The controller manages all VPMem attached devices and guest mounted
27+
// directories.
28+
//
29+
// It is required that all callers:
30+
//
31+
// 1. Obtain a reservation using Reserve().
32+
//
33+
// 2. Use the reservation to Mount() to ensure resource availability.
34+
//
35+
// 3. Call Unmount() to release the reservation and all resources.
36+
//
37+
// If Mount() fails, the caller must call Unmount() to release the reservation
38+
// and all resources.
39+
//
40+
// If Unmount() fails, the caller must call Unmount() again until it succeeds to
41+
// release the reservation and all resources.
42+
type Controller struct {
43+
vm VMVPMemOps
44+
guest LinuxGuestVPMemOps
45+
46+
mu sync.Mutex
47+
48+
// Every call to Reserve gets a unique reservation ID which holds the slot
49+
// index for the device.
50+
reservations map[uuid.UUID]*reservation
51+
52+
// For fast lookup we keep a hostPath to slot mapping for all allocated
53+
// devices.
54+
devicesByPath map[string]uint32
55+
56+
// Tracks all allocated and unallocated available VPMem device slots.
57+
slots []*device.Device
58+
}
59+
60+
func New(maxDevices uint32, vm VMVPMemOps, guest LinuxGuestVPMemOps) *Controller {
61+
return &Controller{
62+
vm: vm,
63+
guest: guest,
64+
mu: sync.Mutex{},
65+
reservations: make(map[uuid.UUID]*reservation),
66+
devicesByPath: make(map[string]uint32),
67+
slots: make([]*device.Device, maxDevices),
68+
}
69+
}
70+
71+
// Reserve creates a referenced counted mapping entry for a VPMem attachment
72+
// based on the device host path.
73+
//
74+
// If an error is returned from this function, it is guaranteed that no
75+
// reservation mapping was made and no Unmount() call is necessary to clean up.
76+
func (c *Controller) Reserve(ctx context.Context, deviceConfig device.DeviceConfig, mountConfig mount.MountConfig) (uuid.UUID, error) {
77+
c.mu.Lock()
78+
defer c.mu.Unlock()
79+
80+
// Generate a new reservation id.
81+
id := uuid.New()
82+
if _, ok := c.reservations[id]; ok {
83+
return uuid.Nil, fmt.Errorf("reservation ID collision")
84+
}
85+
r := &reservation{}
86+
87+
// Determine if this hostPath already had a device known.
88+
if slot, ok := c.devicesByPath[deviceConfig.HostPath]; ok {
89+
r.slot = slot
90+
d := c.slots[slot]
91+
92+
// Verify the caller config is the same.
93+
if !d.Config().Equals(deviceConfig) {
94+
return uuid.Nil, fmt.Errorf("cannot reserve ref on device with different config")
95+
}
96+
97+
if _, err := d.ReserveMount(ctx, mountConfig); err != nil {
98+
return uuid.Nil, fmt.Errorf("reserve mount: %w", err)
99+
}
100+
} else {
101+
// No hostPath was found. Find a slot for the device.
102+
nextSlot := uint32(0)
103+
found := false
104+
for i := uint32(0); i < uint32(len(c.slots)); i++ {
105+
if c.slots[i] == nil {
106+
nextSlot = i
107+
found = true
108+
break
109+
}
110+
}
111+
if !found {
112+
return uuid.Nil, fmt.Errorf("no available slots")
113+
}
114+
115+
// Create the Device and Mount in the reserved states.
116+
d := device.NewReserved(nextSlot, deviceConfig)
117+
if _, err := d.ReserveMount(ctx, mountConfig); err != nil {
118+
return uuid.Nil, fmt.Errorf("reserve mount: %w", err)
119+
}
120+
c.slots[nextSlot] = d
121+
c.devicesByPath[deviceConfig.HostPath] = nextSlot
122+
r.slot = nextSlot
123+
}
124+
125+
// Ensure our reservation is saved for all future operations.
126+
c.reservations[id] = r
127+
return id, nil
128+
}
129+
130+
func (c *Controller) Mount(ctx context.Context, reservation uuid.UUID) (string, error) {
131+
c.mu.Lock()
132+
defer c.mu.Unlock()
133+
134+
if r, ok := c.reservations[reservation]; ok {
135+
d := c.slots[r.slot]
136+
if err := d.AttachToVM(ctx, c.vm); err != nil {
137+
return "", fmt.Errorf("attach device to vm: %w", err)
138+
}
139+
guestPath, err := d.MountToGuest(ctx, c.guest)
140+
if err != nil {
141+
return "", fmt.Errorf("mount to guest: %w", err)
142+
}
143+
return guestPath, nil
144+
}
145+
return "", fmt.Errorf("reservation %s not found", reservation)
146+
}
147+
148+
func (c *Controller) Unmount(ctx context.Context, reservation uuid.UUID) error {
149+
c.mu.Lock()
150+
defer c.mu.Unlock()
151+
152+
if r, ok := c.reservations[reservation]; ok {
153+
d := c.slots[r.slot]
154+
if err := d.UnmountFromGuest(ctx, c.guest); err != nil {
155+
return fmt.Errorf("unmount from guest: %w", err)
156+
}
157+
if err := d.DetachFromVM(ctx, c.vm); err != nil {
158+
return fmt.Errorf("detach device from vm: %w", err)
159+
}
160+
if d.State() == device.DeviceStateDetached {
161+
delete(c.devicesByPath, d.HostPath())
162+
c.slots[r.slot] = nil
163+
}
164+
delete(c.reservations, reservation)
165+
return nil
166+
}
167+
return fmt.Errorf("reservation %s not found", reservation)
168+
}
169+
170+
type reservation struct {
171+
slot uint32
172+
}

0 commit comments

Comments
 (0)