diff --git a/internal/controller/device/scsi/doc.go b/internal/controller/device/scsi/doc.go new file mode 100644 index 0000000000..a3cbd9177b --- /dev/null +++ b/internal/controller/device/scsi/doc.go @@ -0,0 +1,89 @@ +//go:build windows + +// Package scsi manages the lifecycle of SCSI disk attachments on a Hyper-V VM. +// +// It abstracts host-side slot allocation, reference counting, and two-phase +// teardown (guest unplug followed by host removal) with [Manager] as +// the primary implementation. +// +// # Lifecycle +// +// Each disk attachment progresses through the states below. +// The happy path runs down the left column; the error path is on the right. +// +// Allocate slot for the disk +// │ +// ▼ +// ┌─────────────────────┐ +// │ attachmentPending │ +// └──────────┬──────────┘ +// │ +// ┌───────┴────────────────────────────────┐ +// │ AddSCSIDisk succeeds │ AddSCSIDisk fails +// ▼ ▼ +// ┌─────────────────────┐ ┌──────────────────────┐ +// │ attachmentAttached │ │ attachmentInvalid │ +// └──────────┬──────────┘ └──────────┬───────────┘ +// │ unplugFromGuest │ (auto-removed +// │ succeeds │ from map) +// ▼ ▼ +// ┌─────────────────────┐ (removed from map) +// │ attachmentUnplugged │ +// └──────────┬──────────┘ +// │ RemoveSCSIDisk succeeds +// ▼ +// ┌─────────────────────┐ +// │ attachmentDetached │ ← terminal; entry removed from map +// └─────────────────────┘ +// +// ┌─────────────────────┐ +// │ attachmentReserved │ ← no transitions; pre-reserved at construction +// └─────────────────────┘ +// +// State descriptions: +// +// - [attachmentPending]: entered when a new slot is allocated. +// The disk has not yet been added to the SCSI bus. +// - [attachmentAttached]: entered once [AddSCSIDisk] succeeds; +// the disk is on the SCSI bus and available for guest mounts. +// - [attachmentInvalid]: entered when [AddSCSIDisk] fails; +// the map entry is removed. +// - [attachmentUnplugged]: entered once the guest-side unplug completes; +// the guest has released the device but the host has not yet removed it. +// - [attachmentDetached]: terminal state entered once [RemoveSCSIDisk] succeeds. +// - [attachmentReserved]: special state for slots pre-reserved via [New]; +// these are never allocated to new disks and never torn down. +// +// # Reference Counting +// +// Multiple callers may request the same disk (identical host path, type, and +// read-only flag). [Manager.AttachDiskToVM] detects duplicates and increments a +// reference count instead of issuing a second HCS call; the slot is shared. +// [Manager.DetachFromVM] decrements the count and only tears down the attachment +// when it reaches zero. +// +// # Platform Variants +// +// The guest-side unplug step differs between LCOW and WCOW guests and is +// selected via build tags (default for the LCOW shim; "wcow" tag for the WCOW shim): +// +// - LCOW: sends a SCSIDevice removal request to the Guest Compute Service (GCS), +// which hot-unplugs the device from the Linux kernel before the host removes the disk. +// - WCOW: unplugFromGuest is a no-op; Windows handles SCSI hot-unplug +// automatically when the host removes the disk from the VM. +// +// # Usage +// +// mgr := scsi.New(vmSCSI, linuxGuestSCSI, numControllers, reservedSlots) +// +// slot, err := mgr.AttachDiskToVM(ctx, "/path/to/disk.vhdx", scsi.DiskTypeVirtualDisk, false) +// if err != nil { +// // handle error +// } +// +// // ... use slot for guest mounts ... +// +// if err := mgr.DetachFromVM(ctx, slot); err != nil { +// // handle error +// } +package scsi diff --git a/internal/controller/device/scsi/scsi.go b/internal/controller/device/scsi/scsi.go new file mode 100644 index 0000000000..f46c39bd3f --- /dev/null +++ b/internal/controller/device/scsi/scsi.go @@ -0,0 +1,347 @@ +//go:build windows + +package scsi + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/logfields" + "github.com/Microsoft/hcsshim/internal/wclayer" + + "github.com/sirupsen/logrus" +) + +// Manager implements the methods to manage SCSI disk attachment across +// one or more controllers on a Hyper-V VM. +type Manager struct { + // globalMu protects the attachments map and serializes slot allocation across concurrent callers. + globalMu sync.Mutex + + // vmID is the ID for the HCS compute system to which we are attaching disks. + vmID string + + // numControllers is the number of SCSI controllers available on the VM. + // It bounds the (controller, lun) search space when allocating a free slot. + numControllers int + + // attachments tracks every disk currently being attached or already attached + // to the VM. Keyed by VMSlot{Controller, LUN}. + // An absent entry means the slot is free. Access must be guarded by globalMu. + attachments map[VMSlot]*vmAttachment + + // vmSCSI is the host-side SCSI manager used to add and remove disks from the VM. + vmSCSI vmSCSI + + // linuxGuestSCSI is used to perform the guest-side unplug on LCOW prior to detach. + linuxGuestSCSI linuxGuestSCSI +} + +// New creates a new [Manager] instance for managing disk attachments. +// ReservedSlots are never allocated to new disks. +func New( + vmID string, + vmScsi vmSCSI, + linuxGuestScsi linuxGuestSCSI, + numControllers int, + reservedSlots []VMSlot, +) *Manager { + m := &Manager{ + vmID: vmID, + numControllers: numControllers, + attachments: make(map[VMSlot]*vmAttachment), + vmSCSI: vmScsi, + linuxGuestSCSI: linuxGuestScsi, + } + + // Pre-populate attachments with reserved slots so they are never allocated to new disks. + for _, s := range reservedSlots { + m.attachments[s] = &vmAttachment{ + controller: s.Controller, + lun: s.LUN, + refCount: 1, + state: attachmentReserved, + } + } + + return m +} + +// AttachDiskToVM attaches the disk at hostPath to the VM and returns the allocated [VMSlot]. +// If the same disk is already in flight or attached, AttachDiskToVM blocks until the +// original operation completes and then returns the shared slot. +func (m *Manager) AttachDiskToVM( + ctx context.Context, + hostPath string, + diskType DiskType, + readOnly bool, +) (VMSlot, error) { + + log.G(ctx).WithFields(logrus.Fields{ + logfields.HostPath: hostPath, + logfields.DiskType: diskType, + logfields.ReadOnly: readOnly, + }).Debug("Attaching disk to VM") + + // Create the disk config for the VM. + config := &diskConfig{ + hostPath: hostPath, + readOnly: readOnly, + typ: diskType, + } + + // For Virtual and Physical disks, we need to grant VM access to the VHD. + if diskType == DiskTypeVirtualDisk || diskType == DiskTypePassThru { + log.G(ctx).WithField(logfields.HostPath, hostPath).Debug("Granting VM access to disk") + + if err := wclayer.GrantVmAccess(ctx, m.vmID, hostPath); err != nil { + return VMSlot{}, err + } + } + + // Parse EVD-specific fields out of hostPath before forwarding to attachDiskToVM, + if diskType == DiskTypeExtensibleVirtualDisk { + evdType, evdMountPath, err := parseEVDPath(hostPath) + if err != nil { + return VMSlot{}, err + } + config.hostPath = evdMountPath + config.evdType = evdType + } + + return m.attachDiskToVM(ctx, config) +} + +// attachDiskToVM is the internal implementation of [Manager.AttachDiskToVM]. +// It calls [Manager.getOrAllocateSlot] to reuse an existing slot or allocate a new one, +// then drives the HCS add-disk call. On failure the attachment is removed from +// the internal map and the error is returned. +func (m *Manager) attachDiskToVM(ctx context.Context, config *diskConfig) (VMSlot, error) { + // Track the attachment and get the slot for attachment. + // The attachment may be Pending, Attached, or Invalid. + att, err := m.getOrAllocateSlot(ctx, config) + if err != nil { + return VMSlot{}, err + } + + // Acquire the attachment mutex to check the state and potentially drive the attach operation. + att.mu.Lock() + defer att.mu.Unlock() + + ctx, _ = log.WithContext(ctx, logrus.WithFields(logrus.Fields{ + logfields.Controller: att.controller, + logfields.LUN: att.lun, + })) + + log.G(ctx).Debug("received attachment for disk, checking state") + + switch att.state { + case attachmentAttached: + // ============================================================================== + // Found an existing attachment. + // ============================================================================== + att.refCount++ + slot := VMSlot{Controller: att.controller, LUN: att.lun} + + log.G(ctx).Debug("disk already attached to VM, reusing existing slot") + + return slot, nil + case attachmentPending: + // ============================================================================== + // New attachment — we own the slot. + // Other callers requesting this attachment will block on + // att.mu until we transition the state out of Pending. + // ============================================================================== + + log.G(ctx).Debug("performing AddSCSIDisk call to add disk to VM") + + // Perform the host-side HCS call to add the disk at the allocated (controller, lun) slot. + if err = m.vmSCSI.AddSCSIDisk(ctx, hcsschema.Attachment{ + Path: config.hostPath, + Type_: string(config.typ), + ReadOnly: config.readOnly, + ExtensibleVirtualDiskType: config.evdType, + }, att.controller, att.lun); err != nil { + + // Move the state to Invalid so that other goroutines waiting on + // the same attachment see the real failure reason via stateErr. + att.state = attachmentInvalid + att.stateErr = err + + // Delete from the map. Any callers waiting on this attachment + // will see the invalid state and receive the original error. + m.globalMu.Lock() + delete(m.attachments, VMSlot{Controller: att.controller, LUN: att.lun}) + m.globalMu.Unlock() + + return VMSlot{}, fmt.Errorf("add scsi disk %q to vm at controller=%d lun=%d: %w", + config.hostPath, att.controller, att.lun, err) + } + + // Mark the attachment as attached so that future callers can reuse it. + att.state = attachmentAttached + att.refCount++ + + log.G(ctx).Debug("SCSI disk attached to VM") + + return VMSlot{Controller: att.controller, LUN: att.lun}, nil + case attachmentInvalid: + // ============================================================================== + // Found an attachment which failed during HCS operation. + // ============================================================================== + + // Return the original error. The map entry has already been removed + // by the goroutine that drove the failed attach. + return VMSlot{}, fmt.Errorf("previous attempt to attach disk to VM at controller=%d lun=%d failed: %w", + att.controller, att.lun, att.stateErr) + default: + // Unlikely state that should never be observed here. + return VMSlot{}, fmt.Errorf("disk in unexpected state %s during attach", att.state) + } +} + +// getOrAllocateSlot either reuses an existing [vmAttachment] for the same disk config, +// incrementing its reference count, or allocates the first free (controller, lun) slot +// and registers a new [vmAttachment] in the internal map. +func (m *Manager) getOrAllocateSlot(ctx context.Context, config *diskConfig) (*vmAttachment, error) { + m.globalMu.Lock() + defer m.globalMu.Unlock() + + // Reuse an existing attachment for the same disk. + for _, existing := range m.attachments { + if existing != nil && existing.config != nil && existing.config.hostPath == config.hostPath { + return existing, nil + } + } + + log.G(ctx).Debug("no existing attachment found for disk, allocating new slot") + + // Find the first free (controller, lun) pair. + for ctrl := range m.numControllers { + for lun := range numLUNsPerController { + slot := VMSlot{Controller: uint(ctrl), LUN: uint(lun)} + + // if the slot is occupied, then continue to next slot. + if _, occupied := m.attachments[slot]; occupied { + continue + } + + // Found a slot, return it. + log.G(ctx).WithFields(logrus.Fields{ + logfields.Controller: ctrl, + logfields.LUN: lun, + }).Debug("allocating new attachment") + + att := &vmAttachment{ + config: config, + controller: uint(ctrl), + lun: uint(lun), + // Refcount is 0 here since the attachment has not been claimed yet. + refCount: 0, + state: attachmentPending, + } + m.attachments[slot] = att + return att, nil + } + } + + return nil, errors.New("no available scsi slot") +} + +// DetachFromVM detaches the disk at slot from the VM, unplugging it from the guest first. +// If the disk is shared with other callers, DetachFromVM returns without removing it +// until the last caller detaches. +func (m *Manager) DetachFromVM( + ctx context.Context, + slot VMSlot, +) (err error) { + + ctx, _ = log.WithContext(ctx, logrus.WithFields(logrus.Fields{ + logfields.Controller: slot.Controller, + logfields.LUN: slot.LUN, + })) + + log.G(ctx).Debug("Detaching from VM") + + // Under global lock, find the attachment. + m.globalMu.Lock() + // Get the attachment for this slot and unlock global lock. + att := m.attachments[slot] + m.globalMu.Unlock() + + // If there is no attachment, then the slot is already free and there is nothing to detach. + if att == nil { + return nil + } + + if att.state == attachmentReserved { + return fmt.Errorf("cannot release reserved attachment at controller=%d lun=%d", slot.Controller, slot.LUN) + } + + att.mu.Lock() + defer att.mu.Unlock() + + if att.refCount > 1 { + att.refCount-- + // Other callers still hold a reference to this disk. + log.G(ctx).Debug("disk still in use by other callers, not detaching from VM") + return + } + + // If the disk attach failed (AddSCSIDisk never succeeded), but we got the + // entry just prior to removal from map, then state would be invalid. + if att.state == attachmentInvalid { + return nil + } + + // Unplug the device from the guest before removing it from the VM. + // Skip if already unplugged from a previous attempt where RemoveSCSIDisk + // failed after a successful unplug. + if att.state == attachmentAttached { + if err := m.unplugFromGuest(ctx, slot.Controller, slot.LUN); err != nil { + return fmt.Errorf("unplug scsi disk at controller=%d lun=%d from guest: %w", + slot.Controller, slot.LUN, err) + } + att.state = attachmentUnplugged + + log.G(ctx).Debug("disk unplugged from guest") + } + + if att.state == attachmentUnplugged { + if err := m.vmSCSI.RemoveSCSIDisk(ctx, slot.Controller, slot.LUN); err != nil { + return fmt.Errorf("remove scsi disk at controller=%d lun=%d from vm: %w", + slot.Controller, slot.LUN, err) + } + att.state = attachmentDetached + } + + // Cleanup from the map. + m.globalMu.Lock() + delete(m.attachments, slot) + m.globalMu.Unlock() + + log.G(ctx).Debug("SCSI disk detached from VM") + + return nil +} + +// parseEVDPath splits an EVD host path of the form "evd:///" into +// its EVD provider type and the underlying mount path. +// Returns an error if the path does not conform to this scheme. +func parseEVDPath(hostPath string) (evdType, mountPath string, err error) { + if !strings.HasPrefix(hostPath, "evd://") { + return "", "", fmt.Errorf("invalid extensible vhd path: %q", hostPath) + } + + trimmedPath := strings.TrimPrefix(hostPath, "evd://") + separatorIndex := strings.Index(trimmedPath, "/") + if separatorIndex <= 0 { + return "", "", fmt.Errorf("invalid extensible vhd path: %q", hostPath) + } + return trimmedPath[:separatorIndex], trimmedPath[separatorIndex+1:], nil +} diff --git a/internal/controller/device/scsi/scsi_guest_lcow.go b/internal/controller/device/scsi/scsi_guest_lcow.go new file mode 100644 index 0000000000..7bc8fd3433 --- /dev/null +++ b/internal/controller/device/scsi/scsi_guest_lcow.go @@ -0,0 +1,30 @@ +//go:build windows && !wcow + +package scsi + +import ( + "context" + "fmt" + + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" +) + +// unplugFromGuest ejects the SCSI device at (controller, lun) from the Linux guest +// before the host removes it from the VM. +func (m *Manager) unplugFromGuest(ctx context.Context, controller, lun uint) error { + settings := guestresource.SCSIDevice{ + Controller: uint8(controller), + Lun: uint8(lun), + } + + // RemoveSCSIDevice sends a guest modification request that the GCS handles + // by first remapping the logical controller number to the actual kernel-visible + // controller index (HCS and the Linux kernel assign them independently), then + // writing "1" to /sys/bus/scsi/devices//delete. That sysfs write is a + // guest-initiated hot-unplug: the kernel removes the device from its bus and + // flushes any in-flight I/O before the host removes the disk from the VM. + if err := m.linuxGuestSCSI.RemoveSCSIDevice(ctx, settings); err != nil { + return fmt.Errorf("remove scsi device at controller=%d lun=%d from lcow guest: %w", controller, lun, err) + } + return nil +} diff --git a/internal/controller/device/scsi/scsi_guest_wcow.go b/internal/controller/device/scsi/scsi_guest_wcow.go new file mode 100644 index 0000000000..1b1bde8a31 --- /dev/null +++ b/internal/controller/device/scsi/scsi_guest_wcow.go @@ -0,0 +1,11 @@ +//go:build windows && wcow + +package scsi + +import "context" + +// unplugFromGuest is a no-op on Windows guests because Windows handles +// SCSI hot-unplug automatically when the host removes the disk from the VM. +func (m *Manager) unplugFromGuest(_ context.Context, _, _ uint) error { + return nil +} diff --git a/internal/controller/device/scsi/state.go b/internal/controller/device/scsi/state.go new file mode 100644 index 0000000000..1e20e815c7 --- /dev/null +++ b/internal/controller/device/scsi/state.go @@ -0,0 +1,75 @@ +//go:build windows + +package scsi + +// attachmentState represents the current state of a SCSI disk attachment lifecycle. +// +// The normal progression is: +// +// attachmentPending → attachmentAttached → attachmentUnplugged → attachmentDetached +// +// If AddSCSIDisk fails, the owning goroutine moves the attachment to +// attachmentInvalid and records the error. Other goroutines waiting on +// the same attachment observe the invalid state and receive the original +// error. The entry is removed from the map immediately. +// +// attachmentReserved is a special state for pre-reserved slots that never +// transition to any other state. +// +// Full state-transition table: +// +// Current State │ Trigger │ Next State +// ────────────────────────┼────────────────────────────────────┼──────────────────── +// attachmentPending │ AddSCSIDisk succeeds │ attachmentAttached +// attachmentPending │ AddSCSIDisk fails │ attachmentInvalid +// attachmentAttached │ unplugFromGuest succeeds │ attachmentUnplugged +// attachmentUnplugged │ RemoveSCSIDisk succeeds │ attachmentDetached +// attachmentDetached │ (terminal — no further transitions)│ — +// attachmentInvalid │ entry removed from map │ — +// attachmentReserved │ (never transitions) │ — +type attachmentState int + +const ( + // attachmentPending is the initial state; AddSCSIDisk has been called but + // has not yet completed. + attachmentPending attachmentState = iota + + // attachmentAttached means AddSCSIDisk succeeded; the disk is on the SCSI + // bus and available for guest mounts. + attachmentAttached + + // attachmentUnplugged means unplugFromGuest succeeded; the guest has + // released the device but RemoveSCSIDisk has not yet been called. + attachmentUnplugged + + // attachmentDetached means RemoveSCSIDisk succeeded; the disk has been + // fully removed from the VM. This is a terminal state. + attachmentDetached + + // attachmentInvalid means AddSCSIDisk failed. + attachmentInvalid + + // attachmentReserved is used for slots pre-reserved at Manager construction + // time. These must never be handed out or torn down — no transitions are valid. + attachmentReserved +) + +// String returns a human-readable name for the [attachmentState]. +func (s attachmentState) String() string { + switch s { + case attachmentPending: + return "Pending" + case attachmentAttached: + return "Attached" + case attachmentUnplugged: + return "Unplugged" + case attachmentDetached: + return "Detached" + case attachmentInvalid: + return "Invalid" + case attachmentReserved: + return "Reserved" + default: + return "Unknown" + } +} diff --git a/internal/controller/device/scsi/types.go b/internal/controller/device/scsi/types.go new file mode 100644 index 0000000000..c52e5e7e43 --- /dev/null +++ b/internal/controller/device/scsi/types.go @@ -0,0 +1,100 @@ +//go:build windows + +package scsi + +import ( + "context" + "sync" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" +) + +// DiskType identifies the attachment protocol used when adding a disk to the VM's SCSI bus. +type DiskType string + +const ( + // DiskTypeVirtualDisk attaches the disk as a virtual hard disk (VHD/VHDX). + DiskTypeVirtualDisk DiskType = "VirtualDisk" + + // DiskTypePassThru attaches a physical disk directly to the VM with pass-through access. + DiskTypePassThru DiskType = "PassThru" + + // DiskTypeExtensibleVirtualDisk attaches a disk via an extensible virtual disk (EVD) provider. + // The hostPath must be in the form evd:///. + DiskTypeExtensibleVirtualDisk DiskType = "ExtensibleVirtualDisk" +) + +// VMSlot identifies a disk's hardware address on the VM's SCSI bus. +type VMSlot struct { + // Controller is the zero-based SCSI controller index. + Controller uint + // LUN is the logical unit number within the controller. + LUN uint +} + +// ============================================================================== +// Interfaces used by Manager to perform actions on VM and Guest. +// ============================================================================== + +// vmSCSI manages adding and removing SCSI devices for a Utility VM. +type vmSCSI interface { + // AddSCSIDisk hot adds a SCSI disk to the Utility VM. + AddSCSIDisk(ctx context.Context, disk hcsschema.Attachment, controller uint, lun uint) error + + // RemoveSCSIDisk removes a SCSI disk from a Utility VM. + RemoveSCSIDisk(ctx context.Context, controller uint, lun uint) error +} + +// linuxGuestSCSI exposes mapped virtual disk and SCSI device operations in the LCOW guest. +type linuxGuestSCSI interface { + // AddLCOWMappedVirtualDisk maps a virtual disk into the LCOW guest. + AddLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error + // RemoveLCOWMappedVirtualDisk unmaps a virtual disk from the LCOW guest. + RemoveLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error + // RemoveSCSIDevice removes a SCSI device from the guest. + RemoveSCSIDevice(ctx context.Context, settings guestresource.SCSIDevice) error +} + +// ============================================================================== +// INTERNAL DATA STRUCTURES +// Types and constants below this line are unexported and used for state tracking. +// ============================================================================== + +// numLUNsPerController is the maximum number of LUNs per controller, fixed by Hyper-V. +const numLUNsPerController = 64 + +// diskConfig holds the immutable parameters that uniquely identify a disk attachment request. +type diskConfig struct { + hostPath string + readOnly bool + typ DiskType + // evdType is the EVD provider name; only populated when typ is [DiskTypeExtensibleVirtualDisk]. + evdType string +} + +// vmAttachment records one disk's full attachment state and reference count. +type vmAttachment struct { + // mu serializes state transitions and broadcasts completion to concurrent waiters. + mu sync.Mutex + + // config is the immutable disk parameters used to match duplicate attach requests. + config *diskConfig + + // controller and lun are the allocated hardware address on the SCSI bus. + controller uint + lun uint + + // refCount is the number of active callers sharing this attachment. + // Access must be guarded by [Manager.mu]. + refCount uint + + // state tracks the forward-only lifecycle position of this attachment. + // Access must be guarded by [Manager.mu]. + state attachmentState + + // stateErr records the error that caused a transition to [attachmentInvalid]. + // Waiters that find the attachment in the invalid state return this error so + // that every caller sees the original failure reason. + stateErr error +} diff --git a/internal/logfields/fields.go b/internal/logfields/fields.go index dac5a708e5..6cd9d615b7 100644 --- a/internal/logfields/fields.go +++ b/internal/logfields/fields.go @@ -27,6 +27,12 @@ const ( Attempt = "attemptNo" JSON = "json" + // SCSI Constants + + Controller = "controller" + LUN = "lun" + DiskType = "disk-type" + // Time StartTime = "startTime" diff --git a/internal/vm/guestmanager/scsi_lcow.go b/internal/vm/guestmanager/scsi_lcow.go index 65d1b5b24e..528912275d 100644 --- a/internal/vm/guestmanager/scsi_lcow.go +++ b/internal/vm/guestmanager/scsi_lcow.go @@ -11,18 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// LCOWScsiManager exposes mapped virtual disk and SCSI device operations in the LCOW guest. -type LCOWScsiManager interface { - // AddLCOWMappedVirtualDisk maps a virtual disk into the LCOW guest. - AddLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error - // RemoveLCOWMappedVirtualDisk unmaps a virtual disk from the LCOW guest. - RemoveLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error - // RemoveSCSIDevice removes a SCSI device from the guest. - RemoveSCSIDevice(ctx context.Context, settings guestresource.SCSIDevice) error -} - -var _ LCOWScsiManager = (*Guest)(nil) - // AddLCOWMappedVirtualDisk maps a virtual disk into a LCOW guest. func (gm *Guest) AddLCOWMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) error { request := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/vmmanager/scsi.go b/internal/vm/vmmanager/scsi.go index 6af4e8cc96..038ac66a5e 100644 --- a/internal/vm/vmmanager/scsi.go +++ b/internal/vm/vmmanager/scsi.go @@ -11,17 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" ) -// SCSIManager manages adding and removing SCSI devices for a Utility VM. -type SCSIManager interface { - // AddSCSIDisk hot adds a SCSI disk to the Utility VM. - AddSCSIDisk(ctx context.Context, disk hcsschema.Attachment, controller uint, lun uint) error - - // RemoveSCSIDisk removes a SCSI disk from a Utility VM. - RemoveSCSIDisk(ctx context.Context, controller uint, lun uint) error -} - -var _ SCSIManager = (*UtilityVM)(nil) - func (uvm *UtilityVM) AddSCSIDisk(ctx context.Context, disk hcsschema.Attachment, controller uint, lun uint) error { request := &hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeAdd,