Skip to content

Commit

Permalink
Support for storage space data disks
Browse files Browse the repository at this point in the history
This commit adds support in hcsshim to mount a virtual disk backed by storage spaces as a
data disk into a container. In container config the `host_path` in the mount entry
should use the format `space://{storage_space_pool_guid}{storage_space_disk_guid}` to
specify a storage space virtual disk.

Signed-off-by: Amit Barve <ambarve@microsoft.com>
  • Loading branch information
ambarve committed Apr 8, 2021
1 parent 10f8422 commit 4699d69
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 36 deletions.
30 changes: 18 additions & 12 deletions internal/hcsoci/hcsdoc_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mount
// TODO: Mapped pipes to add in v2 schema.
var config mountsConfig
for _, mount := range coi.Spec.Mounts {
if mount.Type != "" {
return nil, fmt.Errorf("invalid container spec - Mount.Type '%s' must not be set", mount.Type)
}

if uvm.IsPipe(mount.Source) {
src, dst := uvm.GetContainerPipeMapping(coi.HostingSystem, mount)
config.mpsv1 = append(config.mpsv1, schema1.MappedPipe{HostPath: src, ContainerPipeName: dst})
Expand All @@ -63,18 +61,26 @@ func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mount
return nil, fmt.Errorf("failed to eval symlinks for mount source %q: %s", mount.Source, err)
}
mdv2.HostPath = src
} else if mount.Type == "virtual-disk" || mount.Type == "physical-disk" || mount.Type == "ExtensibleVirtualDisk" {

mountPath := mount.Source
var err error
if mount.Type == "extensible-virtual-disk" {
_, mountPath, err = uvm.ValidateExtensibleVirtualDiskMount(mount.Source)
if err != nil {
return nil, err
}
}
uvmPath, err := coi.HostingSystem.GetScsiUvmPath(ctx, mountPath)
if err != nil {
return nil, err
}
mdv2.HostPath = uvmPath
} else {
// vsmb mount
uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(ctx, mount.Source, readOnly)
if err != nil {
if err == uvm.ErrNotAttached {
// It could also be a scsi mount.
uvmPath, err = coi.HostingSystem.GetScsiUvmPath(ctx, mount.Source)
if err != nil {
return nil, err
}
} else {
return nil, err
}
return nil, err
}
mdv2.HostPath = uvmPath
}
Expand Down
12 changes: 9 additions & 3 deletions internal/hcsoci/resources_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,15 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R
// Validate each of the mounts. If this is a V2 Xenon, we have to add them as
// VSMB shares to the utility VM. For V1 Xenon and Argons, there's nothing for
// us to do as it's done by HCS.
for i, mount := range coi.Spec.Mounts {
for _, mount := range coi.Spec.Mounts {
if mount.Destination == "" || mount.Source == "" {
return fmt.Errorf("invalid OCI spec - a mount must have both source and a destination: %+v", mount)
}
switch mount.Type {
case "":
case "physical-disk":
case "virtual-disk":
case "ExtensibleVirtualDisk":
default:
return fmt.Errorf("invalid OCI spec - Type '%s' not supported", mount.Type)
}
Expand All @@ -139,15 +140,20 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R
if err != nil {
return errors.Wrapf(err, "adding SCSI physical disk mount %+v", mount)
}
coi.Spec.Mounts[i].Type = ""
r.Add(scsiMount)
} else if mount.Type == "virtual-disk" {
l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI virtual disk for OCI mount")
scsiMount, err := coi.HostingSystem.AddSCSI(ctx, mount.Source, uvmPath, readOnly, uvm.VMAccessTypeIndividual)
if err != nil {
return errors.Wrapf(err, "adding SCSI virtual disk mount %+v", mount)
}
coi.Spec.Mounts[i].Type = ""
r.Add(scsiMount)
} else if mount.Type == "extensible-virtual-disk" {
l.Debug("hcsshim::allocateWindowsResource Hot-adding ExtensibleVirtualDisk")
scsiMount, err := coi.HostingSystem.AddSCSIExtensibleVirtualDisk(ctx, mount.Source, uvmPath, readOnly)
if err != nil {
return errors.Wrapf(err, "adding SCSI EVD mount failed %+v", mount)
}
r.Add(scsiMount)
} else {
if uvm.IsPipe(mount.Source) {
Expand Down
6 changes: 6 additions & 0 deletions internal/schema2/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ type Attachment struct {
CaptureIoAttributionContext bool `json:"CaptureIoAttributionContext,omitempty"`

ReadOnly bool `json:"ReadOnly,omitempty"`

SupportCompressedVolumes bool `json:"SupportCompressedVolumes,omitempty"`

AlwaysAllowSparseFiles bool `json:"AlwaysAllowSparseFiles,omitempty"`

ExtensibleVirtualDiskType string `json:"ExtensibleVirtualDiskType,omitempty"`
}
41 changes: 41 additions & 0 deletions internal/uvm/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"os"
"path/filepath"
"runtime"
"strings"

"github.com/Microsoft/go-winio/pkg/guid"
"github.com/Microsoft/hcsshim/internal/cow"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/log"
Expand Down Expand Up @@ -391,3 +393,42 @@ func (uvm *UtilityVM) CloseGCSConnection() (err error) {
}
return
}

// validateStorageSpaceEVDPath verifies the host path provided for the mount
// type "ExtensibleVirtualDisk" and ExtensibleVirtualDiskType "space"
// The correct path is in the "space://{pool_guid}{disk_guid}" format.
func validateStorageSpaceEVDPath(path string) error {
if !strings.HasPrefix(path, `space://{`) ||
!strings.HasSuffix(path, "}") {
return fmt.Errorf("storage space evd path must follow 'space://{pool_guid}{disk_guid}' format")
}
pool_guid_start := len(`space://{`)
pool_guid_end := pool_guid_start + 36 // length of GUID string
disk_guid_start := pool_guid_end + 2 // skips '}{'
disk_guid_end := disk_guid_start + 36
pool_guid := path[pool_guid_start:pool_guid_end]
disk_guid := path[disk_guid_start:disk_guid_end]
if _, err := guid.FromString(pool_guid); err != nil {
return fmt.Errorf("pool guid is not valid: %w", err)
}
if _, err := guid.FromString(disk_guid); err != nil {
return fmt.Errorf("disk guid is not valid: %w", err)
}
return nil
}

// ValidateExtensibleVirtulaDiskMount verifies that the `hostPath` provided is a valid extensible virtual
// disk path. If it is a valid path then returns the `ExtensibleVirtualDiskType` of this mount and the
// path that should be provided in the mount request to the uvm.
func ValidateExtensibleVirtualDiskMount(hostPath string) (evdType, mountPath string, err error) {
if strings.HasPrefix(hostPath, `space://`) {
if err = validateStorageSpaceEVDPath(hostPath); err != nil {
return
}
evdType = "space"
mountPath = strings.TrimPrefix(hostPath, `space://`)
} else {
err = fmt.Errorf("unsupported Extensible virtual disk: %s", hostPath)
}
return
}
95 changes: 74 additions & 21 deletions internal/uvm/scsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,14 @@ type SCSIMount struct {
refCount uint32
// specifies if this is a readonly layer
readOnly bool
// "VirtualDisk" or "PassThru" disk attachment type.
// "VirtualDisk" or "PassThru" or "ExtensibleVirtualDisk" disk attachment type.
attachmentType string
// serialization ID
serialVersionID uint32
// If attachmentType is "ExtensibleVirtualDisk" then extensibleVirtualDiskType should
// specify the type of it (for e.g "space" for storage spaces). Otherwise this should be
// empty.
extensibleVirtualDiskType string
}

// RefCount returns the current refcount for the SCSI mount.
Expand All @@ -90,13 +94,14 @@ func (sm *SCSIMount) RefCount() uint32 {

func (sm *SCSIMount) logFormat() logrus.Fields {
return logrus.Fields{
"HostPath": sm.HostPath,
"UVMPath": sm.UVMPath,
"isLayer": sm.isLayer,
"refCount": sm.refCount,
"Controller": sm.Controller,
"LUN": sm.LUN,
"SerialVersionID": sm.serialVersionID,
"HostPath": sm.HostPath,
"UVMPath": sm.UVMPath,
"isLayer": sm.isLayer,
"refCount": sm.refCount,
"Controller": sm.Controller,
"LUN": sm.LUN,
"SerialVersionID": sm.serialVersionID,
"ExtensibleVirtualDiskType": sm.extensibleVirtualDiskType,
}
}

Expand All @@ -114,6 +119,21 @@ func newSCSIMount(uvm *UtilityVM, hostPath, uvmPath, attachmentType string, refC
}
}

func newEVDSCSIMount(uvm *UtilityVM, hostPath, uvmPath string, refCount uint32, controller int, lun int32, readOnly bool, evdType string) *SCSIMount {
return &SCSIMount{
vm: uvm,
HostPath: hostPath,
UVMPath: uvmPath,
refCount: refCount,
Controller: controller,
LUN: int32(lun),
readOnly: readOnly,
attachmentType: "ExtensibleVirtualDisk",
serialVersionID: scsiCurrentSerialVersionID,
extensibleVirtualDiskType: evdType,
}
}

// allocateSCSISlot finds the next available slot on the
// SCSI controllers associated with a utility VM to use.
// Lock must be held when calling this function
Expand Down Expand Up @@ -223,7 +243,7 @@ func (uvm *UtilityVM) RemoveSCSI(ctx context.Context, hostPath string) error {
//
// `vmAccess` indicates what access to grant the vm for the hostpath
func (uvm *UtilityVM) AddSCSI(ctx context.Context, hostPath string, uvmPath string, readOnly bool, vmAccess VMAccessType) (*SCSIMount, error) {
return uvm.addSCSIActual(ctx, hostPath, uvmPath, "VirtualDisk", readOnly, vmAccess)
return uvm.addSCSIActual(ctx, hostPath, uvmPath, "VirtualDisk", readOnly, vmAccess, "")
}

// AddSCSIPhysicalDisk attaches a physical disk from the host directly to the
Expand All @@ -235,24 +255,54 @@ func (uvm *UtilityVM) AddSCSI(ctx context.Context, hostPath string, uvmPath stri
//
// `readOnly` set to `true` if the physical disk should be attached read only.
func (uvm *UtilityVM) AddSCSIPhysicalDisk(ctx context.Context, hostPath, uvmPath string, readOnly bool) (*SCSIMount, error) {
return uvm.addSCSIActual(ctx, hostPath, uvmPath, "PassThru", readOnly, VMAccessTypeIndividual)
return uvm.addSCSIActual(ctx, hostPath, uvmPath, "PassThru", readOnly, VMAccessTypeIndividual, "")
}

// addSCSIActual is the implementation behind the external functions AddSCSI and
// AddSCSIPhysicalDisk.
// AddSCSIExtensibleVirtualDisk adds an extensible virtual disk as a SCSI mount
// to the utility VM at the next available location. All such disks which are not actual virtual disks
// but provide the same SCSI interface are added to the UVM as Extensible Virtual disks.
// As of now only the virtual disks backed by storage spaces are supported.
//
// `hostPath` is required. Depending on the type of the extensible virtual disk the format of `hostPath` can
// be different.
// In case of storage space disks the host path must be in the
// `space://{storage_pool_unique_ID}{virtual_disk_unique_ID}` format.
//
// `uvmPath` must be provided in order to be able to use this disk in a container.
//
// `readOnly` set to `true` if the virtual disk should be attached read only.
//
// `vmAccess` indicates what access to grant the vm for the hostpath
func (uvm *UtilityVM) AddSCSIExtensibleVirtualDisk(ctx context.Context, hostPath, uvmPath string, readOnly bool) (*SCSIMount, error) {
if uvmPath == "" {
return nil, fmt.Errorf("uvmPath can not be empty for extensible virtual disk")
}
// check that the EVD is one of the supported types
evdType, mountPath, err := ValidateExtensibleVirtualDiskMount(hostPath)
if err != nil {
return nil, err
}
return uvm.addSCSIActual(ctx, mountPath, uvmPath, "ExtensibleVirtualDisk", readOnly, VMAccessTypeIndividual, evdType)
}

// addSCSIActual is the implementation behind the external functions AddSCSI,
// AddSCSIPhysicalDisk, AddSCSIExtensibleVirtualDisk.
//
// We are in control of everything ourselves. Hence we have ref- counting and
// so-on tracking what SCSI locations are available or used.
//
// `attachmentType` is required and `must` be `VirtualDisk` for vhd/vhdx
// attachments and `PassThru` for physical disk.
// attachments, `PassThru` for physical disk and "ExtensibleVirtualDisk" for Extensible virtual disk.
//
// `readOnly` indicates the attachment should be added read only.
//
// `vmAccess` indicates what access to grant the vm for the hostpath
//
// `evdType` indicates the `ExtensibleVirtualDiskType` if `attachmentType` is "ExtensibleVirtualDisk".
// it should be empty otherwise.
//
// Returns result from calling modify with the given scsi mount
func (uvm *UtilityVM) addSCSIActual(ctx context.Context, hostPath, uvmPath, attachmentType string, readOnly bool, vmAccess VMAccessType) (sm *SCSIMount, err error) {
func (uvm *UtilityVM) addSCSIActual(ctx context.Context, hostPath, uvmPath, attachmentType string, readOnly bool, vmAccess VMAccessType, evdType string) (sm *SCSIMount, err error) {
sm, existed, err := uvm.allocateSCSIMount(ctx, readOnly, hostPath, uvmPath, attachmentType, vmAccess)
if err != nil {
return nil, err
Expand Down Expand Up @@ -280,9 +330,10 @@ func (uvm *UtilityVM) addSCSIActual(ctx context.Context, hostPath, uvmPath, atta
SCSIModification := &hcsschema.ModifySettingRequest{
RequestType: requesttype.Add,
Settings: hcsschema.Attachment{
Path: sm.HostPath,
Type_: attachmentType,
ReadOnly: readOnly,
Path: sm.HostPath,
Type_: attachmentType,
ReadOnly: readOnly,
ExtensibleVirtualDiskType: evdType,
},
ResourcePath: fmt.Sprintf(scsiResourceFormat, strconv.Itoa(sm.Controller), sm.LUN),
}
Expand Down Expand Up @@ -320,10 +371,12 @@ func (uvm *UtilityVM) addSCSIActual(ctx context.Context, hostPath, uvmPath, atta
// Returns the resulting *SCSIMount, a bool indicating if the scsi device was already present,
// and error if any.
func (uvm *UtilityVM) allocateSCSIMount(ctx context.Context, readOnly bool, hostPath, uvmPath, attachmentType string, vmAccess VMAccessType) (*SCSIMount, bool, error) {
// Ensure the utility VM has access
err := grantAccess(ctx, uvm.id, hostPath, vmAccess)
if err != nil {
return nil, false, errors.Wrapf(err, "failed to grant VM access for SCSI mount")
if attachmentType != "ExtensibleVirtualDisk" {
// Ensure the utility VM has access
err := grantAccess(ctx, uvm.id, hostPath, vmAccess)
if err != nil {
return nil, false, errors.Wrapf(err, "failed to grant VM access for SCSI mount")
}
}
// We must hold the lock throughout the lookup (findSCSIAttachment) until
// after the possible allocation (allocateSCSISlot) has been completed to ensure
Expand Down

0 comments on commit 4699d69

Please sign in to comment.