Skip to content

Commit c97839d

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 bd2300d commit c97839d

File tree

9 files changed

+174
-11
lines changed

9 files changed

+174
-11
lines changed

internal/layers/wcow_mount.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,9 +487,14 @@ func mountHypervIsolatedBlockCIMLayers(ctx context.Context, l *wcowBlockCIMLayer
487487

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

490+
shouldFormatWithRefs := false
491+
wopts := vm.GetWCOWCreateOpts()
492+
if wopts.SecurityPolicy != "" {
493+
shouldFormatWithRefs = true
494+
}
490495
scsiMount, err := vm.SCSIManager.AddVirtualDisk(ctx, hostPath, false, vm.ID(), "",
491496
&scsi.MountConfig{
492-
// TODO(ambarve): Add SCSI config to format the scratch in guest
497+
FormatWithRefs: shouldFormatWithRefs,
493498
})
494499
if err != nil {
495500
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 > 30 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/uvm/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ func (uvm *UtilityVM) ScratchEncryptionEnabled() bool {
152152
return uvm.encryptScratch
153153
}
154154

155+
func (uvm *UtilityVM) GetWCOWCreateOpts() *OptionsWCOW {
156+
if uvm.operatingSystem == "linux" {
157+
return nil
158+
}
159+
return uvm.createOpts.(*OptionsWCOW)
160+
}
161+
155162
// OutputHandler is used to process the output from the program run in the UVM.
156163
type OutputHandler func(io.Reader)
157164

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

pkg/cimfs/mount_cim.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import (
1515
"golang.org/x/sys/windows"
1616
)
1717

18+
const (
19+
VolumePathFormat = "\\\\?\\Volume{%s}\\"
20+
)
21+
1822
type MountError struct {
1923
Cim string
2024
Op string
@@ -31,10 +35,6 @@ func (e *MountError) Error() string {
3135
return s
3236
}
3337

34-
const (
35-
VolumePathFormat = "\\\\?\\Volume{%s}\\"
36-
)
37-
3838
// Mount mounts the given cim at a volume with given GUID. Returns the full volume
3939
// path if mount is successful.
4040
func Mount(cimPath string, volumeGUID guid.GUID, mountFlags uint32) (string, error) {

0 commit comments

Comments
 (0)