Skip to content

Commit 07050ee

Browse files
committed
Format container scratch with refs formatter
- Add support to invoke refs formatter for container scratch. - Add tests to validate fsformatter Signed-off-by: Kirtana Ashok <kiashok@microsoft.com>
1 parent 379a2ae commit 07050ee

File tree

7 files changed

+161
-7
lines changed

7 files changed

+161
-7
lines changed

internal/layers/wcow_mount.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,9 +488,10 @@ func mountHypervIsolatedBlockCIMLayers(ctx context.Context, l *wcowBlockCIMLayer
488488

489489
hostPath := filepath.Join(l.scratchLayerPath, "sandbox.vhdx")
490490

491+
// Refs format scratch vhds for c-wcow cases only.
491492
scsiMount, err := vm.SCSIManager.AddVirtualDisk(ctx, hostPath, false, vm.ID(), "",
492493
&scsi.MountConfig{
493-
// TODO(ambarve): Add SCSI config to format the scratch in guest
494+
FormatWithRefs: vm.HasConfidentialPolicy(),
494495
})
495496
if err != nil {
496497
return nil, nil, fmt.Errorf("failed to add SCSI scratch VHD: %w", err)

internal/protocol/guestresource/resources.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const (
2727
// ResourceTypeMappedVirtualDisk is the modify resource type for mapped
2828
// virtual disks
2929
ResourceTypeMappedVirtualDisk guestrequest.ResourceType = "MappedVirtualDisk"
30+
// ResourceTypeMappedVirtualDiskForContainerScratch is the modify resource type
31+
// specifically for refs formatting and mounting scratch vhds for c-wcow cases only.
32+
ResourceTypeMappedVirtualDiskForContainerScratch guestrequest.ResourceType = "MappedVirtualDiskForContainerScratch"
33+
ResourceTypeWCOWBlockCims guestrequest.ResourceType = "WCOWBlockCims"
3034
// ResourceTypeNetwork is the modify resource type for the `NetworkAdapterV2`
3135
// device.
3236
ResourceTypeNetwork guestrequest.ResourceType = "Network"
@@ -51,12 +55,6 @@ const (
5155
ResourceTypeSecurityPolicy guestrequest.ResourceType = "SecurityPolicy"
5256
// ResourceTypePolicyFragment is the modify resource type for injecting policy fragments.
5357
ResourceTypePolicyFragment guestrequest.ResourceType = "SecurityPolicyFragment"
54-
// ResourceTypeWCOWBlockCims is the modify resource type for mounting block cims for hyperv
55-
// wcow containers.
56-
ResourceTypeWCOWBlockCims guestrequest.ResourceType = "WCOWBlockCims"
57-
// ResourceTypeMappedVirtualDiskForContainerScratch is the modify resource type
58-
// specifically for refs formatting and mounting scratch vhds for c-wcow cases only.
59-
ResourceTypeMappedVirtualDiskForContainerScratch guestrequest.ResourceType = "MappedVirtualDiskForContainerScratch"
6058
)
6159

6260
// This class is used by a modify request to add or remove a combined layers

internal/uvm/scsi/backend.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ func mountRequest(controller, lun uint, path string, config *mountConfig, osType
170170
ResourceType: guestresource.ResourceTypeMappedVirtualDisk,
171171
RequestType: guestrequest.RequestTypeAdd,
172172
}
173+
// This option is set only for cwcow scratch disk mount requests
174+
// where we need to format the disk with refs.
175+
// For refs the scratch disk size should > 40 GB.
176+
if config.formatWithRefs {
177+
req.ResourceType = guestresource.ResourceTypeMappedVirtualDiskForContainerScratch
178+
}
179+
173180
switch osType {
174181
case "windows":
175182
// We don't check config.readOnly here, as that will still result in the overall attachment being read-only.
@@ -185,6 +192,7 @@ func mountRequest(controller, lun uint, path string, config *mountConfig, osType
185192
ContainerPath: path,
186193
Lun: int32(lun),
187194
}
195+
188196
case "linux":
189197
req.Settings = guestresource.LCOWMappedVirtualDisk{
190198
MountPath: path,

internal/uvm/scsi/manager.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ type MountConfig struct {
8686
// BlockDev indicates if the device should be mounted as a block device.
8787
// This is only supported for LCOW.
8888
BlockDev bool
89+
// FormatWithRefs indicates to refs format the disk.
90+
// This is only supported for CWCOW scratch disks.
91+
FormatWithRefs bool
8992
}
9093

9194
// Mount represents a SCSI device that has been attached to a VM, and potentially
@@ -162,6 +165,7 @@ func (m *Manager) AddVirtualDisk(
162165
ensureFilesystem: mc.EnsureFilesystem,
163166
filesystem: mc.Filesystem,
164167
blockDev: mc.BlockDev,
168+
formatWithRefs: mc.FormatWithRefs,
165169
}
166170
}
167171
return m.add(ctx,

internal/uvm/scsi/mount.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type mountConfig struct {
4545
options []string
4646
ensureFilesystem bool
4747
filesystem string
48+
formatWithRefs bool
4849
}
4950

5051
func (mm *mountManager) mount(ctx context.Context, controller, lun uint, path string, c *mountConfig) (_ string, err error) {

internal/wclayer/cim/mount.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ func MergeMountBlockCIMLayer(ctx context.Context, mergedLayer *cimfs.BlockCIM, p
108108
if err != nil {
109109
return "", fmt.Errorf("generated cim mount GUID: %w", err)
110110
}
111+
111112
return cimfs.MountMergedBlockCIMs(mergedLayer, parentLayers, mountFlags, volumeGUID)
112113
}
113114

internal/windevice/devicequery_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ package windevice
44

55
import (
66
"context"
7+
"errors"
8+
"fmt"
9+
"log"
710
"path/filepath"
11+
"strings"
12+
"syscall"
813
"testing"
914
"time"
1015

1116
"github.com/Microsoft/go-winio/vhd"
17+
"github.com/Microsoft/hcsshim/internal/fsformatter"
1218
"golang.org/x/sys/windows"
19+
"golang.org/x/sys/windows/svc/mgr"
1320
)
1421

1522
func TestGetDeviceInterfaceInstances(t *testing.T) {
@@ -78,3 +85,137 @@ func TestGetDeviceInterfaceInstances(t *testing.T) {
7885
t.Fatalf("expected interface lists to have same length")
7986
}
8087
}
88+
89+
const (
90+
diskSizeInGB = 35
91+
defaultVHDxBlockSizeMB = 1
92+
)
93+
94+
// startFsformatterDriver checks if fsformatter driver
95+
// has already been loaded and starts the service.
96+
// Returns syscall.ERROR_FILE_NOT_FOUND if driver
97+
// is not loaded.
98+
func startFsformatterDriver() error {
99+
m, err := mgr.Connect()
100+
if err != nil {
101+
log.Fatalf("Failed to connect to service manager: %v", err)
102+
}
103+
defer func() {
104+
_ = m.Disconnect()
105+
}()
106+
107+
// Ensure fsformatter driver is loaded by querying for the service.
108+
serviceName := "kernelfsformatter"
109+
s, err := m.OpenService(serviceName)
110+
if err != nil {
111+
return syscall.ERROR_FILE_NOT_FOUND
112+
}
113+
defer s.Close()
114+
115+
_, err = s.Query()
116+
if err != nil {
117+
return syscall.ERROR_FILE_NOT_FOUND
118+
}
119+
120+
err = s.Start()
121+
if err != nil && !strings.Contains(err.Error(), "An instance of the service is already running") {
122+
return fmt.Errorf("Failed to start service: %w", err)
123+
}
124+
125+
return nil
126+
}
127+
128+
func TestFormatVHDXToReFS(t *testing.T) {
129+
// Ensure fsformatter service is loaded and started
130+
err := startFsformatterDriver()
131+
if err != nil {
132+
// if driver is not loaded already, skip.
133+
if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
134+
t.Skip()
135+
}
136+
t.Fatalf("Failed to start service: %v", err)
137+
}
138+
139+
ctx := context.Background()
140+
initialInterfacesList, err := getDeviceInterfaceInstancesByClass(ctx, &devClassDiskGUID, false)
141+
if err != nil {
142+
t.Fatalf("failed to get initial disk interfaces: %v", err)
143+
}
144+
// We expect to see only one device initially
145+
if len(initialInterfacesList) != 1 {
146+
t.Fatalf("unexpected number of initial disk interfaces: %v", len(initialInterfacesList))
147+
}
148+
t.Logf("initial interface list: %+v\n", initialInterfacesList)
149+
150+
// Create a fixed VHDX of 31 GB (refs needs size to be > 30GB)
151+
tempDir := t.TempDir()
152+
vhdxPath := filepath.Join(tempDir, "test.vhdx")
153+
if err := vhd.CreateVhdx(vhdxPath, diskSizeInGB, defaultVHDxBlockSizeMB); err != nil {
154+
t.Fatalf("failed to create VHDX: %v", err)
155+
}
156+
157+
diskHandle, err := vhd.OpenVirtualDisk(vhdxPath, vhd.VirtualDiskAccessNone, vhd.OpenVirtualDiskFlagNone)
158+
if err != nil {
159+
t.Fatalf("failed to open VHD handle: %s", err)
160+
}
161+
t.Cleanup(func() {
162+
if closeErr := windows.CloseHandle(windows.Handle(diskHandle)); closeErr != nil {
163+
t.Logf("Failed to close VHD handle: %s", closeErr)
164+
}
165+
})
166+
167+
err = vhd.AttachVirtualDisk(diskHandle, vhd.AttachVirtualDiskFlagNone, &vhd.AttachVirtualDiskParameters{Version: 1})
168+
if err != nil {
169+
t.Fatalf("failed to attach VHD: %s", err)
170+
}
171+
t.Cleanup(func() {
172+
if detachErr := vhd.DetachVirtualDisk(diskHandle); detachErr != nil {
173+
t.Logf("failed to detach vhd: %s", detachErr)
174+
}
175+
})
176+
// Disks might take time to show up. Add a small delay
177+
time.Sleep(1 * time.Second)
178+
179+
interfaceListAfterVHDAttach, err := getDeviceInterfaceInstancesByClass(ctx, &devClassDiskGUID, false)
180+
if err != nil {
181+
t.Fatalf("failed to get initial disk interfaces: %v", err)
182+
}
183+
t.Logf("interface list after attaching VHD: %+v\n", interfaceListAfterVHDAttach)
184+
185+
if len(initialInterfacesList) != (len(interfaceListAfterVHDAttach) - 1) {
186+
t.Fatalf("expected to find exactly 1 new interface in the returned interfaces list")
187+
}
188+
189+
for _, iPath := range interfaceListAfterVHDAttach {
190+
// Take only the newly attached vhdx
191+
if iPath == initialInterfacesList[0] {
192+
continue
193+
}
194+
utf16Path, err := windows.UTF16PtrFromString(iPath)
195+
if err != nil {
196+
t.Fatalf("failed to convert interface path [%s] to utf16: %v", iPath, err)
197+
}
198+
199+
handle, err := windows.CreateFile(utf16Path, windows.GENERIC_READ|windows.GENERIC_WRITE,
200+
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
201+
nil, windows.OPEN_EXISTING, 0, 0)
202+
if err != nil {
203+
t.Fatalf("failed to get handle to interface path [%s]: %v", iPath, err)
204+
}
205+
defer windows.Close(handle)
206+
207+
deviceNumber, err := getStorageDeviceNumber(ctx, handle)
208+
if err != nil {
209+
t.Fatalf("failed to get physical device number: %v", err)
210+
}
211+
diskPath := fmt.Sprintf(fsformatter.VirtualDevObjectPathFormat, deviceNumber.DeviceNumber)
212+
t.Logf("diskPath %v", diskPath)
213+
214+
// Invoke refs formatter and ensure it passes.
215+
mountedVolumePath, err := fsformatter.InvokeFsFormatter(ctx, diskPath)
216+
if err != nil {
217+
t.Fatalf("invoking refsFormatter failed: %v", err)
218+
}
219+
t.Logf("mountedVolumePath %v", mountedVolumePath)
220+
}
221+
}

0 commit comments

Comments
 (0)