From 251b502d88be8c56e1bd76376503a7193ae66a21 Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Tue, 10 Feb 2026 16:20:16 +0530 Subject: [PATCH 1/6] internal/vm: split builder and vmmanager layers Refactor UVM responsibilities into clear builder vs vmmanager packages and document the intended 3-layer VM model. Key changes: - Introduce internal/vm/builder for HCS compute system construction. - Introduce internal/vm/vmmanager for host-side lifecycle/resource control. - Move UVM interfaces and shared types into focused locations. - Add internal/vm/README.md describing layer boundaries and usage flow. - Remove legacy internal/vm/hcs files and rename/move implementations. Signed-off-by: Harsh Rawat --- internal/vm/README.md | 61 ++++++++++ internal/vm/builder.go | 74 ------------ internal/vm/builder/boot.go | 34 ++++++ internal/vm/builder/builder.go | 79 ++++++++++++ internal/vm/builder/device.go | 105 ++++++++++++++++ internal/vm/builder/memory.go | 53 ++++++++ internal/vm/builder/numa.go | 25 ++++ internal/vm/builder/processor.go | 28 +++++ internal/vm/builder/scsi.go | 33 +++++ internal/vm/builder/storage.go | 28 +++++ internal/vm/builder/vpmem.go | 24 ++++ internal/vm/builder/vsmb.go | 21 ++++ internal/vm/hcs/boot.go | 33 ----- internal/vm/hcs/builder.go | 96 --------------- internal/vm/hcs/doc.go | 1 - internal/vm/hcs/hcs.go | 78 ------------ internal/vm/hcs/memory.go | 30 ----- internal/vm/hcs/network.go | 36 ------ internal/vm/hcs/pci.go | 42 ------- internal/vm/hcs/plan9.go | 40 ------- internal/vm/hcs/processor.go | 8 -- internal/vm/hcs/scsi.go | 92 -------------- internal/vm/hcs/serial.go | 24 ---- internal/vm/hcs/stats.go | 161 ------------------------- internal/vm/hcs/storage.go | 9 -- internal/vm/hcs/supported.go | 10 -- internal/vm/hcs/vmsocket.go | 43 ------- internal/vm/hcs/vpmem.go | 85 ------------- internal/vm/hcs/vsmb.go | 67 ----------- internal/vm/hcs/windows.go | 14 --- internal/vm/vm.go | 186 +---------------------------- internal/vm/vmmanager/lifetime.go | 142 ++++++++++++++++++++++ internal/vm/vmmanager/network.go | 68 +++++++++++ internal/vm/vmmanager/pci.go | 59 +++++++++ internal/vm/vmmanager/pipe.go | 48 ++++++++ internal/vm/vmmanager/plan9.go | 47 ++++++++ internal/vm/vmmanager/resources.go | 61 ++++++++++ internal/vm/vmmanager/scsi.go | 54 +++++++++ internal/vm/vmmanager/uvm.go | 70 +++++++++++ internal/vm/vmmanager/vmsocket.go | 79 ++++++++++++ internal/vm/vmmanager/vpmem.go | 47 ++++++++ internal/vm/vmmanager/vsmb.go | 47 ++++++++ 42 files changed, 1215 insertions(+), 1127 deletions(-) create mode 100644 internal/vm/README.md delete mode 100644 internal/vm/builder.go create mode 100644 internal/vm/builder/boot.go create mode 100644 internal/vm/builder/builder.go create mode 100644 internal/vm/builder/device.go create mode 100644 internal/vm/builder/memory.go create mode 100644 internal/vm/builder/numa.go create mode 100644 internal/vm/builder/processor.go create mode 100644 internal/vm/builder/scsi.go create mode 100644 internal/vm/builder/storage.go create mode 100644 internal/vm/builder/vpmem.go create mode 100644 internal/vm/builder/vsmb.go delete mode 100644 internal/vm/hcs/boot.go delete mode 100644 internal/vm/hcs/builder.go delete mode 100644 internal/vm/hcs/doc.go delete mode 100644 internal/vm/hcs/hcs.go delete mode 100644 internal/vm/hcs/memory.go delete mode 100644 internal/vm/hcs/network.go delete mode 100644 internal/vm/hcs/pci.go delete mode 100644 internal/vm/hcs/plan9.go delete mode 100644 internal/vm/hcs/processor.go delete mode 100644 internal/vm/hcs/scsi.go delete mode 100644 internal/vm/hcs/serial.go delete mode 100644 internal/vm/hcs/stats.go delete mode 100644 internal/vm/hcs/storage.go delete mode 100644 internal/vm/hcs/supported.go delete mode 100644 internal/vm/hcs/vmsocket.go delete mode 100644 internal/vm/hcs/vpmem.go delete mode 100644 internal/vm/hcs/vsmb.go delete mode 100644 internal/vm/hcs/windows.go create mode 100644 internal/vm/vmmanager/lifetime.go create mode 100644 internal/vm/vmmanager/network.go create mode 100644 internal/vm/vmmanager/pci.go create mode 100644 internal/vm/vmmanager/pipe.go create mode 100644 internal/vm/vmmanager/plan9.go create mode 100644 internal/vm/vmmanager/resources.go create mode 100644 internal/vm/vmmanager/scsi.go create mode 100644 internal/vm/vmmanager/uvm.go create mode 100644 internal/vm/vmmanager/vmsocket.go create mode 100644 internal/vm/vmmanager/vpmem.go create mode 100644 internal/vm/vmmanager/vsmb.go diff --git a/internal/vm/README.md b/internal/vm/README.md new file mode 100644 index 0000000000..ecab131ad2 --- /dev/null +++ b/internal/vm/README.md @@ -0,0 +1,61 @@ +# VM Package + +This directory defines the utility VM (UVM) contracts and separates responsibilities into three layers. The goal is to keep +configuration, host-side management, and guest-side actions distinct so each layer can evolve independently. + +1. **Builder**: constructs an HCS compute system configuration used to create a VM. +2. **VM Manager**: manages host-side VM configuration and lifecycle (NICs, SCSI, VPMem, etc.). +3. **Guest Manager**: intended for guest-side actions (for example, mounting a disk). + +**Note that** this layer does not store UVM host or guest side state. That will be part of the orchestration layer above it. + +## Packages and Responsibilities + +- `internal/vm` + - Shared types used across layers (for example, `GuestOS`, `SCSIDisk`). +- `internal/vm/builder` + - Interface definitions for shaping the VM configuration (`Builder` interface). + - Concrete implementation of `Builder` for building `hcsschema.ComputeSystem` documents. + - Provides a fluent API for configuring all aspects of the VM document. + - Presently, this package is tightly coupled with HCS backend. +- `internal/vm/vmmanager` + - Interface definitions for UVM lifecycle and host-side management. + - Concrete implementation of `UVM` for running and managing a UVM instance. + - Presently, this package is tightly coupled with HCS backend and only runs HCS backed UVMs. + - Owns lifecycle calls (start/terminate/close) and host-side modifications (NICs, SCSI, VPMem, pipes, VSMB, Plan9). + - Allows creation of the UVM using `vmmanager.Create` which takes a `Builder` and produces a running UVM. +- `internal/vm/guestmanager` + - Reserved for guest-level actions such as mounting disks or performing in-guest configuration. + - Currently empty and intended to grow as guest actions are formalized. + +## Typical Flow + +1. Build the config using the builder interfaces. +2. Create the VM using the VM manager. +3. Use manager interfaces for lifecycle and host-side changes. +4. Use guest manager interfaces for in-guest actions (when available). + +## Example (High Level) + +``` +builder, _ := builder.New("owner", vm.Linux) + +// Configure the VM document. +builder.Memory().SetMemoryLimit(1024) +builder.Processor().SetProcessorConfig(&vm.ProcessorConfig{Count: 2}) +// ... other builder configuration + +// Create and start the VM. +uvm, _ := vmmanager.Create(ctx, "uvm-id", builder) +_ = uvm.LifetimeManager().Start(ctx) + +// Apply host-side updates. +_ = uvm.NetworkManager().AddNIC(ctx, nicID, endpointID, macAddr) +``` + +## Layer Boundaries (Quick Reference) + +- **Builder**: static, pre-create configuration only. No host mutations. +- **VM Manager**: host-side changes and lifecycle operations on an existing UVM. +- **Guest Manager**: guest-side actions, scoped to work that requires in-guest context. + diff --git a/internal/vm/builder.go b/internal/vm/builder.go deleted file mode 100644 index 288daad76a..0000000000 --- a/internal/vm/builder.go +++ /dev/null @@ -1,74 +0,0 @@ -package vm - -import ( - "context" -) - -type UVMBuilder interface { - // Create will create the Utility VM in a paused/powered off state with whatever is present in the implementation - // of the interfaces config at the time of the call. - Create(ctx context.Context) (UVM, error) -} - -type MemoryBackingType uint8 - -const ( - MemoryBackingTypeVirtual MemoryBackingType = iota - MemoryBackingTypePhysical -) - -// MemoryConfig holds the memory options that should be configurable for a Utility VM. -type MemoryConfig struct { - BackingType MemoryBackingType - DeferredCommit bool - HotHint bool - ColdHint bool - ColdDiscardHint bool -} - -// MemoryManager handles setting and managing memory configurations for the Utility VM. -type MemoryManager interface { - // SetMemoryLimit sets the amount of memory in megabytes that the Utility VM will be assigned. - SetMemoryLimit(memoryMB uint64) error - // SetMemoryConfig sets an array of different memory configuration options available. This includes things like the - // type of memory to back the VM (virtual/physical). - SetMemoryConfig(config *MemoryConfig) error - // SetMMIOConfig sets memory mapped IO configurations for the Utility VM. - SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) error -} - -// ProcessorManager handles setting and managing processor configurations for the Utility VM. -type ProcessorManager interface { - // SetProcessorCount sets the number of virtual processors that will be assigned to the Utility VM. - SetProcessorCount(count uint32) error -} - -// SerialManager manages setting up serial consoles for the Utility VM. -type SerialManager interface { - // SetSerialConsole sets up a serial console for `port`. Output will be relayed to the listener specified - // by `listenerPath`. For HCS `listenerPath` this is expected to be a path to a named pipe. - SetSerialConsole(port uint32, listenerPath string) error -} - -// BootManager manages boot configurations for the Utility VM. -type BootManager interface { - // SetUEFIBoot sets UEFI configurations for booting a Utility VM. - SetUEFIBoot(dir string, path string, args string) error - // SetLinuxKernelDirectBoot sets Linux direct boot configurations for booting a Utility VM. - SetLinuxKernelDirectBoot(kernel string, initRD string, cmd string) error -} - -// StorageQosManager manages setting storage limits on the Utility VM. -type StorageQosManager interface { - // SetStorageQos sets storage related options for the Utility VM - SetStorageQos(iopsMaximum int64, bandwidthMaximum int64) error -} - -// WindowsConfigManager manages options specific to a Windows host (cpugroups etc.) -type WindowsConfigManager interface { - // SetCPUGroup sets the CPU group that the Utility VM will belong to on a Windows host. - SetCPUGroup(ctx context.Context, id string) error -} - -// LinuxConfigManager manages options specific to a Linux host. -type LinuxConfigManager interface{} diff --git a/internal/vm/builder/boot.go b/internal/vm/builder/boot.go new file mode 100644 index 0000000000..4f5abcc057 --- /dev/null +++ b/internal/vm/builder/boot.go @@ -0,0 +1,34 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/osversion" + + "github.com/pkg/errors" +) + +// BootOptions configures boot settings for the Utility VM. +type BootOptions interface { + // SetUEFIBoot sets UEFI configurations for booting a Utility VM. + SetUEFIBoot(bootEntry *hcsschema.UefiBootEntry) + // SetLinuxKernelDirectBoot sets Linux direct boot configurations for booting a Utility VM. + SetLinuxKernelDirectBoot(options *hcsschema.LinuxKernelDirect) error +} + +var _ BootOptions = (*UtilityVM)(nil) + +func (uvmb *UtilityVM) SetUEFIBoot(bootEntry *hcsschema.UefiBootEntry) { + uvmb.doc.VirtualMachine.Chipset.Uefi = &hcsschema.Uefi{ + BootThis: bootEntry, + } +} + +func (uvmb *UtilityVM) SetLinuxKernelDirectBoot(options *hcsschema.LinuxKernelDirect) error { + if osversion.Get().Build < 18286 { + return errors.New("Linux kernel direct boot requires at least Windows version 18286") + } + uvmb.doc.VirtualMachine.Chipset.LinuxKernelDirect = options + return nil +} diff --git a/internal/vm/builder/builder.go b/internal/vm/builder/builder.go new file mode 100644 index 0000000000..57a9cc7bda --- /dev/null +++ b/internal/vm/builder/builder.go @@ -0,0 +1,79 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/schemaversion" + "github.com/Microsoft/hcsshim/internal/vm" + + "github.com/pkg/errors" +) + +var ( + errAlreadySet = errors.New("field has already been set") + errUnknownGuestOS = errors.New("unknown guest operating system supplied") +) + +// UtilityVM is used to build a schema document for creating a Utility VM. +// It provides methods for configuring various aspects of the Utility VM +// such as memory, processors, devices, boot options, and storage QoS settings. +type UtilityVM struct { + guestOS vm.GuestOS + doc *hcsschema.ComputeSystem + assignedDevices map[hcsschema.VirtualPciFunction]*vPCIDevice +} + +// New returns the concrete builder, and callers are expected to use the +// interface views (for example, NumaOptions, MemoryOptions) as needed. +// This follows the "accept interfaces, return structs" convention. +func New(owner string, guestOS vm.GuestOS) (*UtilityVM, error) { + doc := &hcsschema.ComputeSystem{ + Owner: owner, + SchemaVersion: schemaversion.SchemaV21(), + // Terminate the UVM when the last handle is closed. + // When we need to support impactless updates this will need to be configurable. + ShouldTerminateOnLastHandleClosed: true, + VirtualMachine: &hcsschema.VirtualMachine{ + StopOnReset: true, + Chipset: &hcsschema.Chipset{}, + ComputeTopology: &hcsschema.Topology{ + Memory: &hcsschema.VirtualMachineMemory{}, + Processor: &hcsschema.VirtualMachineProcessor{}, + }, + Devices: &hcsschema.Devices{ + HvSocket: &hcsschema.HvSocket2{ + HvSocketConfig: &hcsschema.HvSocketSystemConfig{ + // Allow administrators and SYSTEM to bind to vsock sockets + // so that we can create a GCS log socket. + DefaultBindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", + ServiceTable: make(map[string]hcsschema.HvSocketServiceConfig), + }, + }, + }, + }, + } + + switch guestOS { + case vm.Windows: + doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{} + case vm.Linux: + doc.VirtualMachine.Devices.Plan9 = &hcsschema.Plan9{} + default: + return nil, errUnknownGuestOS + } + + return &UtilityVM{ + guestOS: guestOS, + doc: doc, + assignedDevices: make(map[hcsschema.VirtualPciFunction]*vPCIDevice), + }, nil +} + +func (uvmb *UtilityVM) GuestOS() vm.GuestOS { + return uvmb.guestOS +} + +func (uvmb *UtilityVM) Get() *hcsschema.ComputeSystem { + return uvmb.doc +} diff --git a/internal/vm/builder/device.go b/internal/vm/builder/device.go new file mode 100644 index 0000000000..03bb23af3f --- /dev/null +++ b/internal/vm/builder/device.go @@ -0,0 +1,105 @@ +//go:build windows + +package builder + +import ( + "strconv" + "strings" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/pkg/errors" +) + +// vPCIDevice represents a vpci device. +type vPCIDevice struct { + // vmbusGUID is the instance ID for this device when it is exposed via VMBus + vmbusGUID string + // deviceInstanceID is the instance ID of the device on the host + deviceInstanceID string + // virtualFunctionIndex is the function index for the pci device to assign + virtualFunctionIndex uint16 + // refCount stores the number of references to this device in the UVM + refCount uint32 +} + +// DeviceOptions configures device settings for the Utility VM. +type DeviceOptions interface { + // AddVPCIDevice adds a PCI device to the Utility VM. + // If the device is already added, we return an error. + AddVPCIDevice(device hcsschema.VirtualPciFunction, numaAffinity bool) error + // AddSCSIController adds a SCSI controller to the Utility VM with the specified ID. + AddSCSIController(id string) + // AddSCSIDisk adds a SCSI disk to the Utility VM under the specified controller and LUN. + AddSCSIDisk(controller string, lun string, disk hcsschema.Attachment) error + // AddVPMemController adds a VPMem controller to the Utility VM with the specified maximum devices and size. + AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) + // AddVPMemDevice adds a VPMem device to the Utility VM under the VPMem controller. + AddVPMemDevice(id string, device hcsschema.VirtualPMemDevice) error + // AddVSMBShare adds a VSMB share to the Utility VM. + AddVSMBShare(share hcsschema.VirtualSmbShare) error + // SetSerialConsole sets up a serial console for `port`. Output will be relayed to the listener specified + // by `listenerPath`. For HCS `listenerPath` this is expected to be a path to a named pipe. + SetSerialConsole(port uint32, listenerPath string) error + // EnableGraphicsConsole enables a graphics console for the Utility VM. + EnableGraphicsConsole() +} + +var _ DeviceOptions = (*UtilityVM)(nil) + +func (uvmb *UtilityVM) AddVPCIDevice(device hcsschema.VirtualPciFunction, numaAffinity bool) error { + _, ok := uvmb.assignedDevices[device] + if ok { + return errors.Wrapf(errAlreadySet, "device %v already assigned to utility VM", device) + } + + vmbusGUID, err := guid.NewV4() + if err != nil { + return errors.Wrap(err, "failed to generate VMBus GUID for device") + } + + var propagateAffinity *bool + if numaAffinity { + propagateAffinity = &numaAffinity + } + + if uvmb.doc.VirtualMachine.Devices.VirtualPci == nil { + uvmb.doc.VirtualMachine.Devices.VirtualPci = make(map[string]hcsschema.VirtualPciDevice) + } + + uvmb.doc.VirtualMachine.Devices.VirtualPci[vmbusGUID.String()] = hcsschema.VirtualPciDevice{ + Functions: []hcsschema.VirtualPciFunction{ + device, + }, + PropagateNumaAffinity: propagateAffinity, + } + + uvmb.assignedDevices[device] = &vPCIDevice{ + vmbusGUID: vmbusGUID.String(), + deviceInstanceID: device.DeviceInstancePath, + virtualFunctionIndex: device.VirtualFunction, + refCount: 1, + } + + return nil +} + +func (uvmb *UtilityVM) SetSerialConsole(port uint32, listenerPath string) error { + if !strings.HasPrefix(listenerPath, `\\.\pipe\`) { + return errors.New("listener for serial console is not a named pipe") + } + + uvmb.doc.VirtualMachine.Devices.ComPorts = map[string]hcsschema.ComPort{ + strconv.Itoa(int(port)): { // "0" would be COM1 + NamedPipe: listenerPath, + }, + } + return nil +} + +func (uvmb *UtilityVM) EnableGraphicsConsole() { + uvmb.doc.VirtualMachine.Devices.Keyboard = &hcsschema.Keyboard{} + uvmb.doc.VirtualMachine.Devices.EnhancedModeVideo = &hcsschema.EnhancedModeVideo{} + uvmb.doc.VirtualMachine.Devices.VideoMonitor = &hcsschema.VideoMonitor{} +} diff --git a/internal/vm/builder/memory.go b/internal/vm/builder/memory.go new file mode 100644 index 0000000000..736fd0647f --- /dev/null +++ b/internal/vm/builder/memory.go @@ -0,0 +1,53 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" +) + +// MemoryOptions configures memory settings for the Utility VM. +type MemoryOptions interface { + // SetMemoryLimit sets the amount of memory in megabytes that the Utility VM will be assigned. + SetMemoryLimit(memoryMB uint64) + // SetMemoryHints sets memory hint settings for the Utility VM. + SetMemoryHints(config *hcsschema.VirtualMachineMemory) + // SetMMIOConfig sets memory mapped IO configurations for the Utility VM. + SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) + // SetFirmwareFallbackMeasuredSlit sets the SLIT type as "FirmwareFallbackMeasured" for the Utility VM. + SetFirmwareFallbackMeasuredSlit() +} + +var _ MemoryOptions = (*UtilityVM)(nil) + +func (uvmb *UtilityVM) SetMemoryLimit(memoryMB uint64) { + uvmb.doc.VirtualMachine.ComputeTopology.Memory.SizeInMB = memoryMB +} + +func (uvmb *UtilityVM) SetMemoryHints(config *hcsschema.VirtualMachineMemory) { + if config == nil { + return + } + + memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory + if config.Backing != nil { + memory.Backing = config.Backing + memory.AllowOvercommit = *config.Backing == hcsschema.MemoryBackingType_VIRTUAL + } + memory.EnableDeferredCommit = config.EnableDeferredCommit + memory.EnableHotHint = config.EnableHotHint + memory.EnableColdHint = config.EnableColdHint + memory.EnableColdDiscardHint = config.EnableColdDiscardHint +} + +func (uvmb *UtilityVM) SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) { + memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory + memory.LowMMIOGapInMB = lowGapMB + memory.HighMMIOBaseInMB = highBaseMB + memory.HighMMIOGapInMB = highGapMB +} + +func (uvmb *UtilityVM) SetFirmwareFallbackMeasuredSlit() { + firmwareFallbackMeasured := hcsschema.VirtualSlitType_FIRMWARE_FALLBACK_MEASURED + uvmb.doc.VirtualMachine.ComputeTopology.Memory.SlitType = &firmwareFallbackMeasured +} diff --git a/internal/vm/builder/numa.go b/internal/vm/builder/numa.go new file mode 100644 index 0000000000..3051fad0ec --- /dev/null +++ b/internal/vm/builder/numa.go @@ -0,0 +1,25 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" +) + +// NumaOptions configures NUMA settings for the Utility VM. +type NumaOptions interface { + // SetNUMAProcessorsSettings sets the NUMA processor settings for the Utility VM. + SetNUMAProcessorsSettings(numaProcessors *hcsschema.NumaProcessors) + // SetNUMASettings sets the NUMA settings for the Utility VM. + SetNUMASettings(numa *hcsschema.Numa) +} + +var _ NumaOptions = (*UtilityVM)(nil) + +func (uvmb *UtilityVM) SetNUMAProcessorsSettings(numaProcessors *hcsschema.NumaProcessors) { + uvmb.doc.VirtualMachine.ComputeTopology.Processor.NumaProcessorsSettings = numaProcessors +} + +func (uvmb *UtilityVM) SetNUMASettings(numa *hcsschema.Numa) { + uvmb.doc.VirtualMachine.ComputeTopology.Numa = numa +} diff --git a/internal/vm/builder/processor.go b/internal/vm/builder/processor.go new file mode 100644 index 0000000000..57b5103e99 --- /dev/null +++ b/internal/vm/builder/processor.go @@ -0,0 +1,28 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" +) + +// ProcessorOptions configures processor settings for the Utility VM. +type ProcessorOptions interface { + // SetProcessorLimits applies Count, Limit, and Weight from the provided config. + SetProcessorLimits(config *hcsschema.VirtualMachineProcessor) + // SetCPUGroup sets the CPU group that the Utility VM will belong to on a Windows host. + SetCPUGroup(cpuGroup *hcsschema.CpuGroup) +} + +var _ ProcessorOptions = (*UtilityVM)(nil) + +func (uvmb *UtilityVM) SetProcessorLimits(config *hcsschema.VirtualMachineProcessor) { + processor := uvmb.doc.VirtualMachine.ComputeTopology.Processor + processor.Count = config.Count + processor.Limit = config.Limit + processor.Weight = config.Weight +} + +func (uvmb *UtilityVM) SetCPUGroup(cpuGroup *hcsschema.CpuGroup) { + uvmb.doc.VirtualMachine.ComputeTopology.Processor.CpuGroup = cpuGroup +} diff --git a/internal/vm/builder/scsi.go b/internal/vm/builder/scsi.go new file mode 100644 index 0000000000..270a56a7b1 --- /dev/null +++ b/internal/vm/builder/scsi.go @@ -0,0 +1,33 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/pkg/errors" +) + +func (uvmb *UtilityVM) AddSCSIController(id string) { + if uvmb.doc.VirtualMachine.Devices.Scsi == nil { + uvmb.doc.VirtualMachine.Devices.Scsi = make(map[string]hcsschema.Scsi) + } + uvmb.doc.VirtualMachine.Devices.Scsi[id] = hcsschema.Scsi{ + Attachments: make(map[string]hcsschema.Attachment), + } +} + +func (uvmb *UtilityVM) AddSCSIDisk(controller string, lun string, disk hcsschema.Attachment) error { + if uvmb.doc.VirtualMachine.Devices.Scsi == nil { + return errors.New("SCSI controller has not been added") + } + + ctrl, ok := uvmb.doc.VirtualMachine.Devices.Scsi[controller] + if !ok { + return errors.Errorf("no scsi controller with id %s found", controller) + } + + ctrl.Attachments[lun] = disk + uvmb.doc.VirtualMachine.Devices.Scsi[controller] = ctrl + + return nil +} diff --git a/internal/vm/builder/storage.go b/internal/vm/builder/storage.go new file mode 100644 index 0000000000..9d0e5ac7bd --- /dev/null +++ b/internal/vm/builder/storage.go @@ -0,0 +1,28 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" +) + +// StorageQoSOptions configures storage QoS settings for the Utility VM. +type StorageQoSOptions interface { + // SetStorageQoS sets storage related options for the Utility VM + SetStorageQoS(options *hcsschema.StorageQoS) +} + +var _ StorageQoSOptions = (*UtilityVM)(nil) + +func (uvmb *UtilityVM) SetStorageQoS(options *hcsschema.StorageQoS) { + if options == nil { + return + } + + if uvmb.doc.VirtualMachine.StorageQoS == nil { + uvmb.doc.VirtualMachine.StorageQoS = &hcsschema.StorageQoS{} + } + + uvmb.doc.VirtualMachine.StorageQoS.BandwidthMaximum = options.BandwidthMaximum + uvmb.doc.VirtualMachine.StorageQoS.IopsMaximum = options.IopsMaximum +} diff --git a/internal/vm/builder/vpmem.go b/internal/vm/builder/vpmem.go new file mode 100644 index 0000000000..c6d56d716e --- /dev/null +++ b/internal/vm/builder/vpmem.go @@ -0,0 +1,24 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/pkg/errors" +) + +func (uvmb *UtilityVM) AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) { + uvmb.doc.VirtualMachine.Devices.VirtualPMem = &hcsschema.VirtualPMemController{ + MaximumCount: maximumDevices, + MaximumSizeBytes: maximumSizeBytes, + Devices: make(map[string]hcsschema.VirtualPMemDevice), + } +} + +func (uvmb *UtilityVM) AddVPMemDevice(id string, device hcsschema.VirtualPMemDevice) error { + if uvmb.doc.VirtualMachine.Devices.VirtualPMem == nil { + return errors.New("VPMem controller has not been added") + } + uvmb.doc.VirtualMachine.Devices.VirtualPMem.Devices[id] = device + return nil +} diff --git a/internal/vm/builder/vsmb.go b/internal/vm/builder/vsmb.go new file mode 100644 index 0000000000..6f3132db4c --- /dev/null +++ b/internal/vm/builder/vsmb.go @@ -0,0 +1,21 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" +) + +func (uvmb *UtilityVM) AddVSMBShare(share hcsschema.VirtualSmbShare) error { + if uvmb.doc.VirtualMachine.Devices.VirtualSmb == nil { + uvmb.doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{ + DirectFileMappingInMB: 1024, // Sensible default, but could be a tuning parameter somewhere + } + } + + uvmb.doc.VirtualMachine.Devices.VirtualSmb.Shares = append( + uvmb.doc.VirtualMachine.Devices.VirtualSmb.Shares, + share, + ) + return nil +} diff --git a/internal/vm/hcs/boot.go b/internal/vm/hcs/boot.go deleted file mode 100644 index a1794a4f2c..0000000000 --- a/internal/vm/hcs/boot.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build windows - -package hcs - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/osversion" - "github.com/pkg/errors" -) - -func (uvmb *utilityVMBuilder) SetUEFIBoot(dir string, path string, args string) error { - uvmb.doc.VirtualMachine.Chipset.Uefi = &hcsschema.Uefi{ - BootThis: &hcsschema.UefiBootEntry{ - DevicePath: path, - DeviceType: "VmbFs", - VmbFsRootPath: dir, - OptionalData: args, - }, - } - return nil -} - -func (uvmb *utilityVMBuilder) SetLinuxKernelDirectBoot(kernel string, initRD string, cmd string) error { - if osversion.Get().Build < 18286 { - return errors.New("Linux kernel direct boot requires at least Windows version 18286") - } - uvmb.doc.VirtualMachine.Chipset.LinuxKernelDirect = &hcsschema.LinuxKernelDirect{ - KernelFilePath: kernel, - InitRdPath: initRD, - KernelCmdLine: cmd, - } - return nil -} diff --git a/internal/vm/hcs/builder.go b/internal/vm/hcs/builder.go deleted file mode 100644 index b3f6026c9b..0000000000 --- a/internal/vm/hcs/builder.go +++ /dev/null @@ -1,96 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - - "github.com/Microsoft/hcsshim/internal/hcs" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/schemaversion" - "github.com/Microsoft/hcsshim/internal/vm" - "github.com/pkg/errors" -) - -var _ vm.UVMBuilder = &utilityVMBuilder{} - -type utilityVMBuilder struct { - id string - guestOS vm.GuestOS - doc *hcsschema.ComputeSystem -} - -func NewUVMBuilder(id string, owner string, guestOS vm.GuestOS) (vm.UVMBuilder, error) { - doc := &hcsschema.ComputeSystem{ - Owner: owner, - SchemaVersion: schemaversion.SchemaV21(), - ShouldTerminateOnLastHandleClosed: true, - VirtualMachine: &hcsschema.VirtualMachine{ - StopOnReset: true, - Chipset: &hcsschema.Chipset{}, - ComputeTopology: &hcsschema.Topology{ - Memory: &hcsschema.VirtualMachineMemory{ - AllowOvercommit: true, - }, - Processor: &hcsschema.VirtualMachineProcessor{}, - }, - Devices: &hcsschema.Devices{ - HvSocket: &hcsschema.HvSocket2{ - HvSocketConfig: &hcsschema.HvSocketSystemConfig{ - // Allow administrators and SYSTEM to bind to vsock sockets - // so that we can create a GCS log socket. - DefaultBindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", - }, - }, - }, - }, - } - - switch guestOS { - case vm.Windows: - doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{} - case vm.Linux: - doc.VirtualMachine.Devices.Plan9 = &hcsschema.Plan9{} - default: - return nil, vm.ErrUnknownGuestOS - } - - return &utilityVMBuilder{ - id: id, - guestOS: guestOS, - doc: doc, - }, nil -} - -func (uvmb *utilityVMBuilder) Create(ctx context.Context) (_ vm.UVM, err error) { - cs, err := hcs.CreateComputeSystem(ctx, uvmb.id, uvmb.doc) - if err != nil { - return nil, errors.Wrap(err, "failed to create hcs compute system") - } - - defer func() { - if err != nil { - _ = cs.Terminate(ctx) - _ = cs.Wait() - } - }() - - backingType := vm.MemoryBackingTypeVirtual - if !uvmb.doc.VirtualMachine.ComputeTopology.Memory.AllowOvercommit { - backingType = vm.MemoryBackingTypePhysical - } - - uvm := &utilityVM{ - id: uvmb.id, - guestOS: uvmb.guestOS, - cs: cs, - backingType: backingType, - } - - properties, err := cs.Properties(ctx) - if err != nil { - return nil, err - } - uvm.vmID = properties.RuntimeID - return uvm, nil -} diff --git a/internal/vm/hcs/doc.go b/internal/vm/hcs/doc.go deleted file mode 100644 index d792dda986..0000000000 --- a/internal/vm/hcs/doc.go +++ /dev/null @@ -1 +0,0 @@ -package hcs diff --git a/internal/vm/hcs/hcs.go b/internal/vm/hcs/hcs.go deleted file mode 100644 index ebf56a7cb6..0000000000 --- a/internal/vm/hcs/hcs.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "sync" - - "github.com/Microsoft/go-winio/pkg/guid" - "github.com/Microsoft/hcsshim/internal/hcs" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" - "github.com/pkg/errors" - "golang.org/x/sys/windows" -) - -var _ vm.UVM = &utilityVM{} - -type utilityVM struct { - id string - guestOS vm.GuestOS - cs *hcs.System - backingType vm.MemoryBackingType - vmmemProcess windows.Handle - vmmemErr error - vmmemOnce sync.Once - vmID guid.GUID -} - -func (uvm *utilityVM) ID() string { - return uvm.id -} - -func (uvm *utilityVM) Start(ctx context.Context) (err error) { - if err := uvm.cs.Start(ctx); err != nil { - return errors.Wrap(err, "failed to start utility VM") - } - return nil -} - -func (uvm *utilityVM) Stop(ctx context.Context) error { - if err := uvm.cs.Terminate(ctx); err != nil { - return errors.Wrap(err, "failed to terminate utility VM") - } - return nil -} - -func (uvm *utilityVM) Pause(ctx context.Context) error { - if err := uvm.cs.Pause(ctx); err != nil { - return errors.Wrap(err, "failed to pause utility VM") - } - return nil -} - -func (uvm *utilityVM) Resume(ctx context.Context) error { - if err := uvm.cs.Resume(ctx); err != nil { - return errors.Wrap(err, "failed to resume utility VM") - } - return nil -} - -func (uvm *utilityVM) Save(ctx context.Context) error { - saveOptions := hcsschema.SaveOptions{ - SaveType: "AsTemplate", - } - if err := uvm.cs.Save(ctx, saveOptions); err != nil { - return errors.Wrap(err, "failed to save utility VM state") - } - return nil -} - -func (uvm *utilityVM) Wait() error { - return uvm.cs.Wait() -} - -func (uvm *utilityVM) ExitError() error { - return uvm.cs.ExitError() -} diff --git a/internal/vm/hcs/memory.go b/internal/vm/hcs/memory.go deleted file mode 100644 index 5934d470fc..0000000000 --- a/internal/vm/hcs/memory.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build windows - -package hcs - -import ( - "github.com/Microsoft/hcsshim/internal/vm" -) - -func (uvmb *utilityVMBuilder) SetMemoryLimit(memoryMB uint64) error { - uvmb.doc.VirtualMachine.ComputeTopology.Memory.SizeInMB = memoryMB - return nil -} - -func (uvmb *utilityVMBuilder) SetMemoryConfig(config *vm.MemoryConfig) error { - memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory - memory.AllowOvercommit = config.BackingType == vm.MemoryBackingTypeVirtual - memory.EnableDeferredCommit = config.DeferredCommit - memory.EnableHotHint = config.HotHint - memory.EnableColdHint = config.ColdHint - memory.EnableColdDiscardHint = config.ColdDiscardHint - return nil -} - -func (uvmb *utilityVMBuilder) SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) error { - memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory - memory.LowMMIOGapInMB = lowGapMB - memory.HighMMIOBaseInMB = highBaseMB - memory.HighMMIOGapInMB = highGapMB - return nil -} diff --git a/internal/vm/hcs/network.go b/internal/vm/hcs/network.go deleted file mode 100644 index f61f6bb4ed..0000000000 --- a/internal/vm/hcs/network.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "fmt" - - "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" -) - -func (uvm *utilityVM) AddNIC(ctx context.Context, nicID, endpointID, macAddr string) error { - request := hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeAdd, - ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, nicID), - Settings: hcsschema.NetworkAdapter{ - EndpointId: endpointID, - MacAddress: macAddr, - }, - } - return uvm.cs.Modify(ctx, request) -} - -func (uvm *utilityVM) RemoveNIC(ctx context.Context, nicID, endpointID, macAddr string) error { - request := hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeRemove, - ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, nicID), - Settings: hcsschema.NetworkAdapter{ - EndpointId: endpointID, - MacAddress: macAddr, - }, - } - return uvm.cs.Modify(ctx, request) -} diff --git a/internal/vm/hcs/pci.go b/internal/vm/hcs/pci.go deleted file mode 100644 index 14c4eb1b05..0000000000 --- a/internal/vm/hcs/pci.go +++ /dev/null @@ -1,42 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "fmt" - - "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" - "github.com/Microsoft/hcsshim/osversion" -) - -func (uvm *utilityVM) AddDevice(ctx context.Context, instanceID, vmbusGUID string) error { - var propagateAffinity *bool - T := true - if osversion.Get().Build >= osversion.V25H1Server { - propagateAffinity = &T - } - request := &hcsschema.ModifySettingRequest{ - ResourcePath: fmt.Sprintf(resourcepaths.VirtualPCIResourceFormat, vmbusGUID), - RequestType: guestrequest.RequestTypeAdd, - Settings: hcsschema.VirtualPciDevice{ - Functions: []hcsschema.VirtualPciFunction{ - { - DeviceInstancePath: instanceID, - }, - }, - PropagateNumaAffinity: propagateAffinity, - }, - } - return uvm.cs.Modify(ctx, request) -} - -func (uvm *utilityVM) RemoveDevice(ctx context.Context, instanceID, vmbusGUID string) error { - request := &hcsschema.ModifySettingRequest{ - ResourcePath: fmt.Sprintf(resourcepaths.VirtualPCIResourceFormat, vmbusGUID), - RequestType: guestrequest.RequestTypeRemove, - } - return uvm.cs.Modify(ctx, request) -} diff --git a/internal/vm/hcs/plan9.go b/internal/vm/hcs/plan9.go deleted file mode 100644 index 23cd7cc030..0000000000 --- a/internal/vm/hcs/plan9.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - - "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" -) - -func (uvm *utilityVM) AddPlan9(ctx context.Context, path, name string, port int32, flags int32, allowed []string) error { - modification := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeAdd, - Settings: hcsschema.Plan9Share{ - Name: name, - AccessName: name, - Path: path, - Port: port, - Flags: flags, - AllowedFiles: allowed, - }, - ResourcePath: resourcepaths.Plan9ShareResourcePath, - } - return uvm.cs.Modify(ctx, modification) -} - -func (uvm *utilityVM) RemovePlan9(ctx context.Context, name string, port int32) error { - modification := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeRemove, - Settings: hcsschema.Plan9Share{ - Name: name, - AccessName: name, - Port: port, - }, - ResourcePath: resourcepaths.Plan9ShareResourcePath, - } - return uvm.cs.Modify(ctx, modification) -} diff --git a/internal/vm/hcs/processor.go b/internal/vm/hcs/processor.go deleted file mode 100644 index 7bda4abb93..0000000000 --- a/internal/vm/hcs/processor.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build windows - -package hcs - -func (uvmb *utilityVMBuilder) SetProcessorCount(count uint32) error { - uvmb.doc.VirtualMachine.ComputeTopology.Processor.Count = count - return nil -} diff --git a/internal/vm/hcs/scsi.go b/internal/vm/hcs/scsi.go deleted file mode 100644 index ad2582d16e..0000000000 --- a/internal/vm/hcs/scsi.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "fmt" - "strconv" - - "github.com/pkg/errors" - - "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" - "github.com/Microsoft/hcsshim/internal/vm" -) - -func (uvmb *utilityVMBuilder) AddSCSIController(id uint32) error { - if uvmb.doc.VirtualMachine.Devices.Scsi == nil { - uvmb.doc.VirtualMachine.Devices.Scsi = make(map[string]hcsschema.Scsi, 1) - } - uvmb.doc.VirtualMachine.Devices.Scsi[strconv.Itoa(int(id))] = hcsschema.Scsi{ - Attachments: make(map[string]hcsschema.Attachment), - } - return nil -} - -func (uvmb *utilityVMBuilder) AddSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string, typ vm.SCSIDiskType, readOnly bool) error { - if uvmb.doc.VirtualMachine.Devices.Scsi == nil { - return errors.New("no SCSI controller found") - } - - ctrl, ok := uvmb.doc.VirtualMachine.Devices.Scsi[strconv.Itoa(int(controller))] - if !ok { - return fmt.Errorf("no scsi controller with index %d found", controller) - } - - ctrl.Attachments[strconv.Itoa(int(lun))] = hcsschema.Attachment{ - Path: path, - Type_: string(typ), - ReadOnly: readOnly, - } - - return nil -} - -func (uvmb *utilityVMBuilder) RemoveSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string) error { - return vm.ErrNotSupported -} - -func (uvm *utilityVM) AddSCSIController(id uint32) error { - return vm.ErrNotSupported -} - -func (uvm *utilityVM) AddSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string, typ vm.SCSIDiskType, readOnly bool) error { - diskTypeString, err := getSCSIDiskTypeString(typ) - if err != nil { - return err - } - request := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeAdd, - Settings: hcsschema.Attachment{ - Path: path, - Type_: diskTypeString, - ReadOnly: readOnly, - }, - ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, strconv.Itoa(int(controller)), lun), - } - return uvm.cs.Modify(ctx, request) -} - -func (uvm *utilityVM) RemoveSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string) error { - request := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeRemove, - ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, strconv.Itoa(int(controller)), lun), - } - - return uvm.cs.Modify(ctx, request) -} - -func getSCSIDiskTypeString(typ vm.SCSIDiskType) (string, error) { - switch typ { - case vm.SCSIDiskTypeVHD1: - fallthrough - case vm.SCSIDiskTypeVHDX: - return "VirtualDisk", nil - case vm.SCSIDiskTypePassThrough: - return "PassThru", nil - default: - return "", fmt.Errorf("unsupported SCSI disk type: %d", typ) - } -} diff --git a/internal/vm/hcs/serial.go b/internal/vm/hcs/serial.go deleted file mode 100644 index ef6541c5c1..0000000000 --- a/internal/vm/hcs/serial.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build windows - -package hcs - -import ( - "strconv" - "strings" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/pkg/errors" -) - -func (uvmb *utilityVMBuilder) SetSerialConsole(port uint32, listenerPath string) error { - if !strings.HasPrefix(listenerPath, `\\.\pipe\`) { - return errors.New("listener for serial console is not a named pipe") - } - - uvmb.doc.VirtualMachine.Devices.ComPorts = map[string]hcsschema.ComPort{ - strconv.Itoa(int(port)): { // "0" would be COM1 - NamedPipe: listenerPath, - }, - } - return nil -} diff --git a/internal/vm/hcs/stats.go b/internal/vm/hcs/stats.go deleted file mode 100644 index 8cd3edbc3e..0000000000 --- a/internal/vm/hcs/stats.go +++ /dev/null @@ -1,161 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "strings" - - "github.com/Microsoft/go-winio/pkg/guid" - "github.com/Microsoft/go-winio/pkg/process" - "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/log" - "github.com/Microsoft/hcsshim/internal/vm" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "golang.org/x/sys/windows" -) - -// checkProcess checks if the process identified by the given pid has a name -// matching `desiredProcessName`, and is running as a user with domain -// `desiredDomain` and user name `desiredUser`. If the process matches, it -// returns a handle to the process. If the process does not match, it returns -// 0. -func checkProcess(ctx context.Context, pid uint32, desiredProcessName string, desiredDomain string, desiredUser string) (p windows.Handle, err error) { - desiredProcessName = strings.ToUpper(desiredProcessName) - desiredDomain = strings.ToUpper(desiredDomain) - desiredUser = strings.ToUpper(desiredUser) - - p, err = windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.PROCESS_VM_READ, false, pid) - if err != nil { - return 0, err - } - - defer func(openedProcess windows.Handle) { - // If we don't return this process handle, close it so it doesn't leak. - if p == 0 { - windows.Close(openedProcess) - } - }(p) - - // Querying vmmem's image name as a win32 path returns ERROR_GEN_FAILURE - // for some reason, so we query it as an NT path instead. - name, err := process.QueryFullProcessImageName(p, process.ImageNameFormatNTPath) - if err != nil { - return 0, err - } - if strings.ToUpper(name) == desiredProcessName { - var t windows.Token - if err := windows.OpenProcessToken(p, windows.TOKEN_QUERY, &t); err != nil { - return 0, err - } - defer t.Close() - tUser, err := t.GetTokenUser() - if err != nil { - return 0, err - } - user, domain, _, err := tUser.User.Sid.LookupAccount("") - if err != nil { - return 0, err - } - log.G(ctx).WithFields(logrus.Fields{ - "name": name, - "domain": domain, - "user": user, - }).Debug("checking vmmem process identity") - if strings.ToUpper(domain) == desiredDomain && strings.ToUpper(user) == desiredUser { - return p, nil - } - } - return 0, nil -} - -// lookupVMMEM locates the vmmem process for a VM given the VM ID. It returns -// a handle to the vmmem process. The lookup is implemented by enumerating all -// processes on the system, and finding a process with full name "vmmem", -// running as "NT VIRTUAL MACHINE\". -func lookupVMMEM(ctx context.Context, vmID guid.GUID) (proc windows.Handle, err error) { - vmIDStr := strings.ToUpper(vmID.String()) - log.G(ctx).WithField("vmID", vmIDStr).Debug("looking up vmmem") - - pids, err := process.EnumProcesses() - if err != nil { - return 0, errors.Wrap(err, "failed to enumerate processes") - } - for _, pid := range pids { - p, err := checkProcess(ctx, pid, "vmmem", "NT VIRTUAL MACHINE", vmIDStr) - if err != nil { - // Checking the process could fail for a variety of reasons, such as - // the process exiting since we called EnumProcesses, or not having - // access to open the process (even as SYSTEM). In the case of an - // error, we just log and continue looking at the other processes. - log.G(ctx).WithField("pid", pid).Debug("failed to check process") - continue - } - if p != 0 { - log.G(ctx).WithField("pid", pid).Debug("found vmmem match") - return p, nil - } - } - return 0, errors.New("failed to find matching vmmem process") -} - -// getVMMEMProcess returns a handle to the vmmem process associated with this -// UVM. It only does the actual process lookup once, after which it caches the -// process handle in the UVM object. -func (uvm *utilityVM) getVMMEMProcess(ctx context.Context) (windows.Handle, error) { - uvm.vmmemOnce.Do(func() { - uvm.vmmemProcess, uvm.vmmemErr = lookupVMMEM(ctx, uvm.vmID) - }) - return uvm.vmmemProcess, uvm.vmmemErr -} - -func (uvm *utilityVM) Stats(ctx context.Context) (*stats.VirtualMachineStatistics, error) { - s := &stats.VirtualMachineStatistics{} - props, err := uvm.cs.PropertiesV2(ctx, hcsschema.PTStatistics, hcsschema.PTMemory) - if err != nil { - return nil, err - } - - s.Processor = &stats.VirtualMachineProcessorStatistics{} - s.Processor.TotalRuntimeNS = uint64(props.Statistics.Processor.TotalRuntime100ns * 100) - s.Memory = &stats.VirtualMachineMemoryStatistics{} - - if uvm.backingType == vm.MemoryBackingTypePhysical { - // If the uvm is physically backed we set the working set to the total amount allocated - // to the UVM. AssignedMemory returns the number of 4KB pages. Will always be 4KB - // regardless of what the UVMs actual page size is so we don't need that information. - if props.Memory != nil { - s.Memory.WorkingSetBytes = props.Memory.VirtualMachineMemory.AssignedMemory * 4096 - } - } else { - // The HCS properties does not return sufficient information to calculate - // working set size for a VA-backed UVM. To work around this, we instead - // locate the vmmem process for the VM, and query that process's working set - // instead, which will be the working set for the VM. - vmmemProc, err := uvm.getVMMEMProcess(ctx) - if err != nil { - return nil, err - } - memCounters, err := process.GetProcessMemoryInfo(vmmemProc) - if err != nil { - return nil, err - } - s.Memory.WorkingSetBytes = uint64(memCounters.WorkingSetSize) - } - - if props.Memory != nil { - s.Memory.VirtualNodeCount = props.Memory.VirtualNodeCount - s.Memory.VmMemory = &stats.VirtualMachineMemory{} - s.Memory.VmMemory.AvailableMemory = props.Memory.VirtualMachineMemory.AvailableMemory - s.Memory.VmMemory.AvailableMemoryBuffer = props.Memory.VirtualMachineMemory.AvailableMemoryBuffer - s.Memory.VmMemory.ReservedMemory = props.Memory.VirtualMachineMemory.ReservedMemory - s.Memory.VmMemory.AssignedMemory = props.Memory.VirtualMachineMemory.AssignedMemory - s.Memory.VmMemory.SlpActive = props.Memory.VirtualMachineMemory.SlpActive - s.Memory.VmMemory.BalancingEnabled = props.Memory.VirtualMachineMemory.BalancingEnabled - s.Memory.VmMemory.DmOperationInProgress = props.Memory.VirtualMachineMemory.DmOperationInProgress - } - - return s, nil -} diff --git a/internal/vm/hcs/storage.go b/internal/vm/hcs/storage.go deleted file mode 100644 index edc7d4dd6e..0000000000 --- a/internal/vm/hcs/storage.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build windows - -package hcs - -func (uvmb *utilityVMBuilder) SetStorageQos(iopsMaximum int64, bandwidthMaximum int64) error { - uvmb.doc.VirtualMachine.StorageQoS.BandwidthMaximum = int32(bandwidthMaximum) - uvmb.doc.VirtualMachine.StorageQoS.IopsMaximum = int32(iopsMaximum) - return nil -} diff --git a/internal/vm/hcs/supported.go b/internal/vm/hcs/supported.go deleted file mode 100644 index 0fb4453a80..0000000000 --- a/internal/vm/hcs/supported.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build windows - -package hcs - -import "github.com/Microsoft/hcsshim/internal/vm" - -func (uvm *utilityVM) Supported(resource vm.Resource, op vm.ResourceOperation) bool { - // For now at least HCS supports everything we care about. - return true -} diff --git a/internal/vm/hcs/vmsocket.go b/internal/vm/hcs/vmsocket.go deleted file mode 100644 index 49c50b88b8..0000000000 --- a/internal/vm/hcs/vmsocket.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "net" - - "github.com/Microsoft/go-winio" - "github.com/Microsoft/go-winio/pkg/guid" - "github.com/Microsoft/hcsshim/internal/vm" - "github.com/pkg/errors" -) - -func (uvm *utilityVM) VMSocketListen(ctx context.Context, listenType vm.VMSocketType, connID interface{}) (net.Listener, error) { - switch listenType { - case vm.HvSocket: - serviceGUID, ok := connID.(guid.GUID) - if !ok { - return nil, errors.New("parameter passed to hvsocketlisten is not a GUID") - } - return uvm.hvSocketListen(ctx, serviceGUID) - case vm.VSock: - port, ok := connID.(uint32) - if !ok { - return nil, errors.New("parameter passed to vsocklisten is not the right type") - } - return uvm.vsockListen(ctx, port) - default: - return nil, errors.New("unknown vmsocket type requested") - } -} - -func (uvm *utilityVM) hvSocketListen(ctx context.Context, serviceID guid.GUID) (net.Listener, error) { - return winio.ListenHvsock(&winio.HvsockAddr{ - VMID: uvm.vmID, - ServiceID: serviceID, - }) -} - -func (uvm *utilityVM) vsockListen(ctx context.Context, port uint32) (net.Listener, error) { - return nil, vm.ErrNotSupported -} diff --git a/internal/vm/hcs/vpmem.go b/internal/vm/hcs/vpmem.go deleted file mode 100644 index 915a10e7f7..0000000000 --- a/internal/vm/hcs/vpmem.go +++ /dev/null @@ -1,85 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - "fmt" - "strconv" - - "github.com/pkg/errors" - - "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" - "github.com/Microsoft/hcsshim/internal/vm" -) - -func (uvmb *utilityVMBuilder) AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) error { - uvmb.doc.VirtualMachine.Devices.VirtualPMem = &hcsschema.VirtualPMemController{ - MaximumCount: maximumDevices, - MaximumSizeBytes: maximumSizeBytes, - } - uvmb.doc.VirtualMachine.Devices.VirtualPMem.Devices = make(map[string]hcsschema.VirtualPMemDevice) - return nil -} - -func (uvmb *utilityVMBuilder) AddVPMemDevice(ctx context.Context, id uint32, path string, readOnly bool, imageFormat vm.VPMemImageFormat) error { - if uvmb.doc.VirtualMachine.Devices.VirtualPMem == nil { - return errors.New("VPMem controller has not been added") - } - imageFormatString, err := getVPMemImageFormatString(imageFormat) - if err != nil { - return err - } - uvmb.doc.VirtualMachine.Devices.VirtualPMem.Devices[strconv.Itoa(int(id))] = hcsschema.VirtualPMemDevice{ - HostPath: path, - ReadOnly: readOnly, - ImageFormat: imageFormatString, - } - return nil -} - -func (uvmb *utilityVMBuilder) RemoveVPMemDevice(ctx context.Context, id uint32, path string) error { - return vm.ErrNotSupported -} - -func (uvm *utilityVM) AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) error { - return vm.ErrNotSupported -} - -func (uvm *utilityVM) AddVPMemDevice(ctx context.Context, id uint32, path string, readOnly bool, imageFormat vm.VPMemImageFormat) error { - imageFormatString, err := getVPMemImageFormatString(imageFormat) - if err != nil { - return err - } - request := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeAdd, - Settings: hcsschema.VirtualPMemDevice{ - HostPath: path, - ReadOnly: readOnly, - ImageFormat: imageFormatString, - }, - ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, id), - } - return uvm.cs.Modify(ctx, request) -} - -func (uvm *utilityVM) RemoveVPMemDevice(ctx context.Context, id uint32, path string) error { - request := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeRemove, - ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, id), - } - return uvm.cs.Modify(ctx, request) -} - -func getVPMemImageFormatString(imageFormat vm.VPMemImageFormat) (string, error) { - switch imageFormat { - case vm.VPMemImageFormatVHD1: - return "Vhd1", nil - case vm.VPMemImageFormatVHDX: - return "Vhdx", nil - default: - return "", fmt.Errorf("unsupported VPMem image format: %d", imageFormat) - } -} diff --git a/internal/vm/hcs/vsmb.go b/internal/vm/hcs/vsmb.go deleted file mode 100644 index 3cb1696c50..0000000000 --- a/internal/vm/hcs/vsmb.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - - "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" - "github.com/Microsoft/hcsshim/internal/vm" -) - -func (uvmb *utilityVMBuilder) AddVSMB(ctx context.Context, path string, name string, allowed []string, options *vm.VSMBOptions) error { - uvmb.doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{ - DirectFileMappingInMB: 1024, // Sensible default, but could be a tuning parameter somewhere - Shares: []hcsschema.VirtualSmbShare{ - { - Name: name, - Path: path, - AllowedFiles: allowed, - Options: vmVSMBOptionsToHCS(options), - }, - }, - } - return nil -} - -func (uvmb *utilityVMBuilder) RemoveVSMB(ctx context.Context, name string) error { - return vm.ErrNotSupported -} - -func vmVSMBOptionsToHCS(options *vm.VSMBOptions) *hcsschema.VirtualSmbShareOptions { - return &hcsschema.VirtualSmbShareOptions{ - ReadOnly: options.ReadOnly, - ShareRead: options.ShareRead, - CacheIo: options.CacheIo, - NoOplocks: options.NoOplocks, - NoDirectmap: options.NoDirectMap, - TakeBackupPrivilege: options.TakeBackupPrivilege, - PseudoOplocks: options.PseudoOplocks, - PseudoDirnotify: options.PseudoDirnotify, - } -} - -func (uvm *utilityVM) AddVSMB(ctx context.Context, path string, name string, allowed []string, options *vm.VSMBOptions) error { - modification := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeAdd, - Settings: hcsschema.VirtualSmbShare{ - Name: name, - Options: vmVSMBOptionsToHCS(options), - Path: path, - AllowedFiles: allowed, - }, - ResourcePath: resourcepaths.VSMBShareResourcePath, - } - return uvm.cs.Modify(ctx, modification) -} - -func (uvm *utilityVM) RemoveVSMB(ctx context.Context, name string) error { - modification := &hcsschema.ModifySettingRequest{ - RequestType: guestrequest.RequestTypeRemove, - Settings: hcsschema.VirtualSmbShare{Name: name}, - ResourcePath: resourcepaths.VSMBShareResourcePath, - } - return uvm.cs.Modify(ctx, modification) -} diff --git a/internal/vm/hcs/windows.go b/internal/vm/hcs/windows.go deleted file mode 100644 index 40ab8629c6..0000000000 --- a/internal/vm/hcs/windows.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build windows - -package hcs - -import ( - "context" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func (uvmb *utilityVMBuilder) SetCPUGroup(ctx context.Context, id string) error { - uvmb.doc.VirtualMachine.ComputeTopology.Processor.CpuGroup = &hcsschema.CpuGroup{Id: id} - return nil -} diff --git a/internal/vm/vm.go b/internal/vm/vm.go index b7bfd9e25c..a7a97a9bef 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -1,85 +1,6 @@ -package vm - -import ( - "context" - "errors" - "net" - - "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" -) - -var ( - ErrNotSupported = errors.New("virtstack does not support the operation") - ErrAlreadySet = errors.New("field has already been set") - ErrUnsupportedGuestOS = errors.New("virtstack does not support the guest operating system") - ErrUnknownGuestOS = errors.New("unknown guest operating system supplied") -) - -// UVM is an abstraction around a lightweight virtual machine. It houses core lifecycle methods such as Create -// Start, and Stop and also several optional nested interfaces that can be used to determine what the virtual machine -// supports and to configure these resources. -type UVM interface { - // ID will return a string identifier for the Utility VM. - ID() string - - // Start will power on the Utility VM and put it into a running state. This will boot the guest OS and start all of the - // devices configured on the machine. - Start(ctx context.Context) error - - // Stop will shutdown the Utility VM and place it into a terminated state. - Stop(ctx context.Context) error - - // Pause will place the Utility VM into a paused state. The guest OS will be halted and any devices will have be in a - // a suspended state. Save can be used to snapshot the current state of the virtual machine, and Resume can be used to - // place the virtual machine back into a running state. - Pause(ctx context.Context) error - - // Resume will put a previously paused Utility VM back into a running state. The guest OS will resume operation from the point - // in time it was paused and all devices should be un-suspended. - Resume(ctx context.Context) error +//go:build windows - // Save will snapshot the state of the Utility VM at the point in time when the VM was paused. - Save(ctx context.Context) error - - // Wait synchronously waits for the Utility VM to shutdown or terminate. A call to stop will trigger this - // to unblock. - Wait() error - - // Stats returns statistics about the Utility VM. This includes things like assigned memory, available memory, - // processor runtime etc. - Stats(ctx context.Context) (*stats.VirtualMachineStatistics, error) - - // Supported returns if the virt stack supports a given operation on a resource. - Supported(resource Resource, operation ResourceOperation) bool - - // ExitError will return any error if the Utility VM exited unexpectedly, or if the Utility VM experienced an - // error after Wait returned, it will return the wait error. - ExitError() error -} - -// Resource refers to the type of a resource on a Utility VM. -type Resource uint8 - -const ( - VPMem = iota - SCSI - Network - VSMB - PCI - Plan9 - Memory - Processor - CPUGroup -) - -// Operation refers to the type of operation to perform on a given resource. -type ResourceOperation uint8 - -const ( - Add ResourceOperation = iota - Remove - Update -) +package vm // GuestOS signifies the guest operating system that a Utility VM will be running. type GuestOS string @@ -88,106 +9,3 @@ const ( Windows GuestOS = "windows" Linux GuestOS = "linux" ) - -// SCSIDiskType refers to the disk type of the scsi device. This is either a vhd, vhdx, or a physical disk. -type SCSIDiskType uint8 - -const ( - SCSIDiskTypeVHD1 SCSIDiskType = iota - SCSIDiskTypeVHDX - SCSIDiskTypePassThrough -) - -// SCSIManager manages adding and removing SCSI devices for a Utility VM. -type SCSIManager interface { - // AddSCSIController adds a SCSI controller to the Utility VM configuration document. - AddSCSIController(id uint32) error - // AddSCSIDisk adds a SCSI disk to the configuration document if in a precreated state, or hot adds a - // SCSI disk to the Utility VM if the VM is running. - AddSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string, typ SCSIDiskType, readOnly bool) error - // RemoveSCSIDisk removes a SCSI disk from a Utility VM. - RemoveSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string) error -} - -// VPMemImageFormat refers to the image type of the vpmem block device. This is either a vhd or vhdx. -type VPMemImageFormat uint8 - -const ( - VPMemImageFormatVHD1 VPMemImageFormat = iota - VPMemImageFormatVHDX -) - -// VPMemManager manages adding and removing virtual persistent memory devices for a Utility VM. -type VPMemManager interface { - // AddVPMemController adds a new virtual pmem controller to the Utility VM. - // `maximumDevices` specifies how many vpmem devices will be present in the guest. - // `maximumSizeBytes` specifies the maximum size allowed for a vpmem device. - AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) error - // AddVPMemDevice adds a virtual pmem device to the Utility VM. - AddVPMemDevice(ctx context.Context, id uint32, path string, readOnly bool, imageFormat VPMemImageFormat) error - // RemoveVpmemDevice removes a virtual pmem device from the Utility VM. - RemoveVPMemDevice(ctx context.Context, id uint32, path string) error -} - -// NetworkManager manages adding and removing network adapters for a Utility VM. -type NetworkManager interface { - // AddNIC adds a network adapter to the Utility VM. `nicID` should be a string representation of a - // Windows GUID. - AddNIC(ctx context.Context, nicID string, endpointID string, macAddr string) error - // RemoveNIC removes a network adapter from the Utility VM. `nicID` should be a string representation of a - // Windows GUID. - RemoveNIC(ctx context.Context, nicID string, endpointID string, macAddr string) error -} - -// PCIManager manages assiging pci devices to a Utility VM. This is Windows specific at the moment. -type PCIManager interface { - // AddDevice adds the pci device identified by `instanceID` to the Utility VM. - // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/instance-ids - AddDevice(ctx context.Context, instanceID string, vmbusGUID string) error - // RemoveDevice removes the pci device identified by `instanceID` from the Utility VM. - RemoveDevice(ctx context.Context, instanceID string, vmbusGUID string) error -} - -// VMSocketType refers to which hypervisor socket transport type to use. -type VMSocketType uint8 - -const ( - HvSocket VMSocketType = iota - VSock -) - -// VMSocketManager manages configuration for a hypervisor socket transport. This includes sockets such as -// HvSocket and Vsock. -type VMSocketManager interface { - // VMSocketListen will create the requested vmsocket type and listen on the address specified by `connID`. - // For HvSocket the type expected is a GUID, for Vsock it's a port of type uint32. - VMSocketListen(ctx context.Context, socketType VMSocketType, connID interface{}) (net.Listener, error) -} - -// VSMBOptions -type VSMBOptions struct { - ReadOnly bool - CacheIo bool - NoDirectMap bool - PseudoOplocks bool - ShareRead bool - TakeBackupPrivilege bool - NoOplocks bool - PseudoDirnotify bool -} - -// VSMBManager manages adding virtual smb shares to a Utility VM. -type VSMBManager interface { - // AddVSMB adds a virtual smb share to a running Utility VM. - AddVSMB(ctx context.Context, hostPath string, name string, allowedFiles []string, options *VSMBOptions) error - // RemoveVSMB removes a virtual smb share from a running Utility VM. - RemoveVSMB(ctx context.Context, name string) error -} - -// Plan9Manager manages adding plan 9 shares to a Utility VM. -type Plan9Manager interface { - // AddPlan9 adds a plan 9 share to a running Utility VM. - AddPlan9(ctx context.Context, path, name string, port int32, flags int32, allowed []string) error - // RemovePlan9 removes a plan 9 share from a running Utility VM. - RemovePlan9(ctx context.Context, name string, port int32) error -} diff --git a/internal/vm/vmmanager/lifetime.go b/internal/vm/vmmanager/lifetime.go new file mode 100644 index 0000000000..8aadcf411c --- /dev/null +++ b/internal/vm/vmmanager/lifetime.go @@ -0,0 +1,142 @@ +//go:build windows + +package vmmanager + +import ( + "context" + + "github.com/Microsoft/go-winio/pkg/guid" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +type LifetimeManager interface { + // ID will return a string identifier for the Utility VM. + ID() string + + // RuntimeID will return the Hyper-V VM GUID for the Utility VM. + // + // Only valid after the utility VM has been created. + RuntimeID() guid.GUID + + // OS will return the operating system type of the Utility VM. This is typically either "windows" or "linux". + OS() vm.GuestOS + + // Start will power on the Utility VM and put it into a running state. This will boot the guest OS and start all of the + // devices configured on the machine. + Start(ctx context.Context) error + + // Terminate will forcefully power off the Utility VM. + Terminate(ctx context.Context) error + + // Close terminates and releases resources associated with the utility VM. + Close(ctx context.Context) error + + // Pause will place the Utility VM into a paused state. The guest OS will be halted and any devices will have be in a + // a suspended state. Save can be used to snapshot the current state of the virtual machine, and Resume can be used to + // place the virtual machine back into a running state. + Pause(ctx context.Context) error + + // Resume will put a previously paused Utility VM back into a running state. The guest OS will resume operation from the point + // in time it was paused and all devices should be un-suspended. + Resume(ctx context.Context) error + + // Save will snapshot the state of the Utility VM at the point in time when the VM was paused. + Save(ctx context.Context, options hcsschema.SaveOptions) error + + // Wait synchronously waits for the Utility VM to terminate. + Wait(ctx context.Context) error + + // PropertiesV2 returns the properties of the Utility VM. + PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error) + + // ExitError will return any error if the Utility VM exited unexpectedly, or if the Utility VM experienced an + // error after Wait returned, it will return the wait error. + ExitError() error +} + +var _ LifetimeManager = (*UtilityVM)(nil) + +// ID returns the ID of the utility VM. +func (uvm *UtilityVM) ID() string { + return uvm.id +} + +// RuntimeID returns the runtime ID of the utility VM. +func (uvm *UtilityVM) RuntimeID() guid.GUID { + return uvm.vmID +} + +// OS returns the operating system of the utility VM. +func (uvm *UtilityVM) OS() vm.GuestOS { + return uvm.guestOS +} + +// Start starts the utility VM. +func (uvm *UtilityVM) Start(ctx context.Context) (err error) { + if err := uvm.cs.Start(ctx); err != nil { + return errors.Wrap(err, "failed to start utility VM") + } + return nil +} + +// Terminate terminates the utility VM. +func (uvm *UtilityVM) Terminate(ctx context.Context) error { + if err := uvm.cs.Terminate(ctx); err != nil { + return errors.Wrap(err, "failed to terminate utility VM") + } + return nil +} + +// Close closes the utility VM and releases all associated resources. +func (uvm *UtilityVM) Close(ctx context.Context) error { + if err := uvm.cs.CloseCtx(ctx); err != nil { + return errors.Wrap(err, "failed to close utility VM") + } + return nil +} + +// Pause pauses the utility VM. +func (uvm *UtilityVM) Pause(ctx context.Context) error { + if err := uvm.cs.Pause(ctx); err != nil { + return errors.Wrap(err, "failed to pause utility VM") + } + return nil +} + +// Resume resumes the utility VM. +func (uvm *UtilityVM) Resume(ctx context.Context) error { + if err := uvm.cs.Resume(ctx); err != nil { + return errors.Wrap(err, "failed to resume utility VM") + } + return nil +} + +// Save saves the state of the utility VM as a template. +func (uvm *UtilityVM) Save(ctx context.Context, options hcsschema.SaveOptions) error { + if err := uvm.cs.Save(ctx, options); err != nil { + return errors.Wrap(err, "failed to save utility VM state") + } + return nil +} + +// Wait waits for the utility VM to exit and returns any error that occurred during execution. +func (uvm *UtilityVM) Wait(ctx context.Context) error { + return uvm.cs.WaitCtx(ctx) +} + +// PropertiesV2 returns the properties of the utility VM from HCS. +func (uvm *UtilityVM) PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error) { + props, err := uvm.cs.PropertiesV2(ctx, types...) + if err != nil { + return nil, errors.Wrap(err, "failed to get properties from HCS") + } + + return props, nil +} + +// ExitError returns the exit error of the utility VM, if it has exited. +func (uvm *UtilityVM) ExitError() error { + return uvm.cs.ExitError() +} diff --git a/internal/vm/vmmanager/network.go b/internal/vm/vmmanager/network.go new file mode 100644 index 0000000000..29f03c7abc --- /dev/null +++ b/internal/vm/vmmanager/network.go @@ -0,0 +1,68 @@ +//go:build windows + +package vmmanager + +import ( + "context" + "fmt" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/pkg/errors" +) + +// NetworkManager manages adding and removing network adapters for a Utility VM. +type NetworkManager interface { + // AddNIC adds a network adapter to the Utility VM. `nicID` should be a string representation of a + // Windows GUID. + AddNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error + + // RemoveNIC removes a network adapter from the Utility VM. `nicID` should be a string representation of a + // Windows GUID. + RemoveNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error + + // UpdateNIC updates the configuration of a network adapter on the Utility VM. + UpdateNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error +} + +var _ NetworkManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) AddNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error { + request := hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, nicID), + Settings: settings, + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to add NIC %s", nicID) + } + return nil +} + +func (uvm *UtilityVM) RemoveNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error { + request := hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, nicID), + Settings: settings, + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to remove NIC %s", nicID) + } + return nil +} + +func (uvm *UtilityVM) UpdateNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error { + if settings == nil { + return errors.New("network adapter settings cannot be nil") + } + req := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeUpdate, + ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, nicID), + Settings: settings, + } + if err := uvm.cs.Modify(ctx, req); err != nil { + return errors.Wrapf(err, "failed to update NIC %s", nicID) + } + return nil +} diff --git a/internal/vm/vmmanager/pci.go b/internal/vm/vmmanager/pci.go new file mode 100644 index 0000000000..19e7a168f8 --- /dev/null +++ b/internal/vm/vmmanager/pci.go @@ -0,0 +1,59 @@ +//go:build windows + +package vmmanager + +import ( + "context" + "fmt" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/osversion" + "github.com/pkg/errors" +) + +// PCIManager manages assiging pci devices to a Utility VM. This is Windows specific at the moment. +type PCIManager interface { + // AddDevice adds the pci device identified by `instanceID` to the Utility VM. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/instance-ids + AddDevice(ctx context.Context, vmbusGUID string, function hcsschema.VirtualPciFunction) error + + // RemoveDevice removes the pci device identified by `vmbusGUID` from the Utility VM. + RemoveDevice(ctx context.Context, vmbusGUID string) error +} + +var _ PCIManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) AddDevice(ctx context.Context, vmbusGUID string, function hcsschema.VirtualPciFunction) error { + var propagateAffinity *bool + T := true + if osversion.Get().Build >= osversion.V25H1Server { + propagateAffinity = &T + } + request := &hcsschema.ModifySettingRequest{ + ResourcePath: fmt.Sprintf(resourcepaths.VirtualPCIResourceFormat, vmbusGUID), + RequestType: guestrequest.RequestTypeAdd, + Settings: hcsschema.VirtualPciDevice{ + Functions: []hcsschema.VirtualPciFunction{ + function, + }, + PropagateNumaAffinity: propagateAffinity, + }, + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to add PCI device %s", vmbusGUID) + } + return nil +} + +func (uvm *UtilityVM) RemoveDevice(ctx context.Context, vmbusGUID string) error { + request := &hcsschema.ModifySettingRequest{ + ResourcePath: fmt.Sprintf(resourcepaths.VirtualPCIResourceFormat, vmbusGUID), + RequestType: guestrequest.RequestTypeRemove, + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to remove PCI device %s", vmbusGUID) + } + return nil +} diff --git a/internal/vm/vmmanager/pipe.go b/internal/vm/vmmanager/pipe.go new file mode 100644 index 0000000000..c72e55f77f --- /dev/null +++ b/internal/vm/vmmanager/pipe.go @@ -0,0 +1,48 @@ +//go:build windows + +package vmmanager + +import ( + "context" + "fmt" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/pkg/errors" +) + +// PipeManager manages adding and removing named pipes for a Utility VM. +type PipeManager interface { + // AddPipe adds a named pipe to the Utility VM. + AddPipe(ctx context.Context, hostPath string) error + + // RemovePipe removes a named pipe from the Utility VM. + RemovePipe(ctx context.Context, hostPath string) error +} + +var _ PipeManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) AddPipe(ctx context.Context, hostPath string) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + ResourcePath: fmt.Sprintf(resourcepaths.MappedPipeResourceFormat, hostPath), + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to add pipe %s to uvm", hostPath) + } + + return nil +} + +func (uvm *UtilityVM) RemovePipe(ctx context.Context, hostPath string) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.MappedPipeResourceFormat, hostPath), + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to remove pipe %s from uvm", hostPath) + } + + return nil +} diff --git a/internal/vm/vmmanager/plan9.go b/internal/vm/vmmanager/plan9.go new file mode 100644 index 0000000000..92ad66d9d2 --- /dev/null +++ b/internal/vm/vmmanager/plan9.go @@ -0,0 +1,47 @@ +//go:build windows + +package vmmanager + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/pkg/errors" +) + +// Plan9Manager manages adding plan 9 shares to a Utility VM. +type Plan9Manager interface { + // AddPlan9 adds a plan 9 share to a running Utility VM. + AddPlan9(ctx context.Context, settings hcsschema.Plan9Share) error + + // RemovePlan9 removes a plan 9 share from a running Utility VM. + RemovePlan9(ctx context.Context, settings hcsschema.Plan9Share) error +} + +var _ Plan9Manager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) AddPlan9(ctx context.Context, settings hcsschema.Plan9Share) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + ResourcePath: resourcepaths.Plan9ShareResourcePath, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to add Plan9 share %s", settings.Name) + } + return nil +} + +func (uvm *UtilityVM) RemovePlan9(ctx context.Context, settings hcsschema.Plan9Share) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + ResourcePath: resourcepaths.Plan9ShareResourcePath, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to remove Plan9 share %s", settings.Name) + } + return nil +} diff --git a/internal/vm/vmmanager/resources.go b/internal/vm/vmmanager/resources.go new file mode 100644 index 0000000000..d32f1b7f19 --- /dev/null +++ b/internal/vm/vmmanager/resources.go @@ -0,0 +1,61 @@ +//go:build windows + +package vmmanager + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/pkg/errors" +) + +type ResourceManager interface { + // SetCPUGroup assigns the Utility VM to a cpu group. + SetCPUGroup(ctx context.Context, settings *hcsschema.CpuGroup) error + + // UpdateCPULimits updates the CPU limits for the Utility VM. + // `limit` is the percentage of CPU cycles that the Utility VM is allowed to use. + // `weight` is the relative weight of the Utility VM compared to other VMs when CPU cycles are contended. + // `reservation` is the percentage of CPU cycles that are reserved for the Utility VM. + // `maximumFrequencyMHz` is the maximum frequency in MHz that the Utility VM can use. + UpdateCPULimits(ctx context.Context, settings *hcsschema.ProcessorLimits) error + + // UpdateMemory makes a call to the VM's orchestrator to update the VM's size in MB + UpdateMemory(ctx context.Context, memory uint64) error +} + +var _ ResourceManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) SetCPUGroup(ctx context.Context, settings *hcsschema.CpuGroup) error { + modification := &hcsschema.ModifySettingRequest{ + ResourcePath: resourcepaths.CPUGroupResourcePath, + Settings: settings, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrap(err, "failed to modify CPU Group") + } + return nil +} + +func (uvm *UtilityVM) UpdateCPULimits(ctx context.Context, settings *hcsschema.ProcessorLimits) error { + modification := &hcsschema.ModifySettingRequest{ + ResourcePath: resourcepaths.CPULimitsResourcePath, + Settings: settings, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrap(err, "failed to modify CPU Limits") + } + return nil +} + +func (uvm *UtilityVM) UpdateMemory(ctx context.Context, memory uint64) error { + modification := &hcsschema.ModifySettingRequest{ + ResourcePath: resourcepaths.MemoryResourcePath, + Settings: memory, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrap(err, "failed to modify memory") + } + return nil +} diff --git a/internal/vm/vmmanager/scsi.go b/internal/vm/vmmanager/scsi.go new file mode 100644 index 0000000000..67c1916714 --- /dev/null +++ b/internal/vm/vmmanager/scsi.go @@ -0,0 +1,54 @@ +//go:build windows + +package vmmanager + +import ( + "context" + "fmt" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/pkg/errors" +) + +// 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, + Settings: hcsschema.Attachment{ + Path: disk.Path, + Type_: disk.Type_, + ReadOnly: disk.ReadOnly, + ExtensibleVirtualDiskType: disk.ExtensibleVirtualDiskType, + }, + ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, guestrequest.ScsiControllerGuids[controller], lun), + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to add SCSI disk %s", disk.Path) + } + + return nil +} + +func (uvm *UtilityVM) RemoveSCSIDisk(ctx context.Context, controller uint, lun uint) error { + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, guestrequest.ScsiControllerGuids[controller], lun), + } + + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to remove SCSI disk %s", request.ResourcePath) + } + return nil +} diff --git a/internal/vm/vmmanager/uvm.go b/internal/vm/vmmanager/uvm.go new file mode 100644 index 0000000000..7298592750 --- /dev/null +++ b/internal/vm/vmmanager/uvm.go @@ -0,0 +1,70 @@ +//go:build windows + +package vmmanager + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/hcs" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/logfields" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/Microsoft/hcsshim/internal/vm/builder" + + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// UtilityVM is an abstraction around a lightweight virtual machine. +// It houses core lifecycle methods such as Create, Start, and Stop and +// also several optional methods that can be used to determine what the virtual machine +// supports and to configure these resources. +type UtilityVM struct { + id string + guestOS vm.GuestOS + vmID guid.GUID + cs *hcs.System +} + +// Create creates a new utility VM with the given ID and builder configuration. +// +// This method returns the concrete UtilityVM and Callers +// can use the interface views (for example, LifetimeManager, NetworkManager) +// as needed. This follows the "accept interfaces, return structs" convention. +func Create(ctx context.Context, id string, builder *builder.UtilityVM) (*UtilityVM, error) { + config := builder.Get() + + cs, err := hcs.CreateComputeSystem(ctx, id, config) + if err != nil { + return nil, errors.Wrap(err, "failed to create compute system") + } + + defer func() { + if cs != nil { + _ = cs.Terminate(ctx) + _ = cs.WaitCtx(ctx) + } + }() + + uvm := &UtilityVM{ + id: id, + guestOS: builder.GuestOS(), + } + + // Cache the VM ID of the utility VM. + properties, err := cs.Properties(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get compute system properties") + } + uvm.vmID = properties.RuntimeID + uvm.cs = cs + cs = nil + + log.G(ctx).WithFields(logrus.Fields{ + logfields.UVMID: uvm.id, + "runtime-id": uvm.vmID.String(), + }).Debug("created utility VM") + + return uvm, nil +} diff --git a/internal/vm/vmmanager/vmsocket.go b/internal/vm/vmmanager/vmsocket.go new file mode 100644 index 0000000000..746a9ec72c --- /dev/null +++ b/internal/vm/vmmanager/vmsocket.go @@ -0,0 +1,79 @@ +//go:build windows + +package vmmanager + +import ( + "context" + "fmt" + "net" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/pkg/errors" +) + +// VMSocketManager manages configuration for a hypervisor socket transport. This includes sockets such as +// HvSocket and Vsock. +type VMSocketManager interface { + // VMSocketListen will create the requested HvSocket and listen on the address specified by `connID`. + VMSocketListen(connID guid.GUID) (net.Listener, error) + + // UpdateHvSocketService will update the configuration for the HvSocket service with the specified `serviceID`. + UpdateHvSocketService(ctx context.Context, serviceID string, config *hcsschema.HvSocketServiceConfig) error + + // RemoveHvSocketService will remove the HvSocket service with the specified `serviceID` from the Utility VM. + RemoveHvSocketService(ctx context.Context, serviceID string) error +} + +var _ VMSocketManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) VMSocketListen(connID guid.GUID) (net.Listener, error) { + return winio.ListenHvsock(&winio.HvsockAddr{ + VMID: uvm.vmID, + ServiceID: connID, + }) +} + +// UpdateHvSocketService calls HCS to update/create the hvsocket service for +// the UVM. Takes in a service ID and the hvsocket service configuration. If there is no +// entry for the service ID already it will be created. The same call on HvSockets side +// handles the Create/Update/Delete cases based on what is passed in. Here is the logic +// for the call. +// +// 1. If the service ID does not currently exist in the service table, it will be created +// with whatever descriptors and state was specified (disabled or not). +// 2. If the service already exists and empty descriptors and Disabled is passed in for the +// service config, the service will be removed. +// 3. Otherwise any combination that is not Disabled && Empty descriptors will just update the +// service. +// +// If the request is crafted with Disabled = True and empty descriptors, then this function +// will behave identically to a call to RemoveHvSocketService. Prefer RemoveHvSocketService for this +// behavior as the relevant fields are set on HCS' side. +func (uvm *UtilityVM) UpdateHvSocketService(ctx context.Context, serviceID string, config *hcsschema.HvSocketServiceConfig) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeUpdate, + ResourcePath: fmt.Sprintf(resourcepaths.HvSocketConfigResourceFormat, serviceID), + Settings: config, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to update HvSocket service %s", serviceID) + } + return nil +} + +// RemoveHvSocketService will remove an hvsocket service entry if it exists. +func (uvm *UtilityVM) RemoveHvSocketService(ctx context.Context, serviceID string) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.HvSocketConfigResourceFormat, serviceID), + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to remove HvSocket service %s", serviceID) + } + return nil +} diff --git a/internal/vm/vmmanager/vpmem.go b/internal/vm/vmmanager/vpmem.go new file mode 100644 index 0000000000..46c4e28db7 --- /dev/null +++ b/internal/vm/vmmanager/vpmem.go @@ -0,0 +1,47 @@ +//go:build windows + +package vmmanager + +import ( + "context" + "fmt" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/pkg/errors" +) + +// VPMemManager manages adding and removing virtual persistent memory devices for a Utility VM. +type VPMemManager interface { + // AddVPMemDevice adds a virtual pmem device to the Utility VM. + AddVPMemDevice(ctx context.Context, id uint32, settings hcsschema.VirtualPMemDevice) error + + // RemoveVPMemDevice removes a virtual pmem device from the Utility VM. + RemoveVPMemDevice(ctx context.Context, id uint32) error +} + +var _ VPMemManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) AddVPMemDevice(ctx context.Context, id uint32, settings hcsschema.VirtualPMemDevice) error { + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, id), + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to add VPMem device %d", id) + } + return nil +} + +func (uvm *UtilityVM) RemoveVPMemDevice(ctx context.Context, id uint32) error { + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, id), + } + if err := uvm.cs.Modify(ctx, request); err != nil { + return errors.Wrapf(err, "failed to remove VPMem device %d", id) + } + return nil +} diff --git a/internal/vm/vmmanager/vsmb.go b/internal/vm/vmmanager/vsmb.go new file mode 100644 index 0000000000..106eaa0c44 --- /dev/null +++ b/internal/vm/vmmanager/vsmb.go @@ -0,0 +1,47 @@ +//go:build windows + +package vmmanager + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/pkg/errors" +) + +// VSMBManager manages adding virtual smb shares to a Utility VM. +type VSMBManager interface { + // AddVSMB adds a virtual smb share to a running Utility VM. + AddVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error + + // RemoveVSMB removes a virtual smb share from a running Utility VM. + RemoveVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error +} + +var _ VSMBManager = (*UtilityVM)(nil) + +func (uvm *UtilityVM) AddVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + ResourcePath: resourcepaths.VSMBShareResourcePath, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to add VSMB share %s", settings.Name) + } + return nil +} + +func (uvm *UtilityVM) RemoveVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + ResourcePath: resourcepaths.VSMBShareResourcePath, + } + if err := uvm.cs.Modify(ctx, modification); err != nil { + return errors.Wrapf(err, "failed to remove VSMB share %s", settings.Name) + } + return nil +} From 7519c12e1366414d071fe81ed60fd71741688d69 Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Sun, 15 Feb 2026 17:33:08 +0530 Subject: [PATCH 2/6] Add unit tests for VM builder configuration and device management Signed-off-by: Harsh Rawat --- internal/vm/builder/boot_test.go | 64 ++++++++++++++++++ internal/vm/builder/builder_test.go | 46 +++++++++++++ internal/vm/builder/device_test.go | 62 +++++++++++++++++ internal/vm/builder/memory_test.go | 86 ++++++++++++++++++++++++ internal/vm/builder/numa_test.go | 97 +++++++++++++++++++++++++++ internal/vm/builder/processor_test.go | 26 +++++++ internal/vm/builder/scsi_test.go | 34 ++++++++++ internal/vm/builder/storage_test.go | 26 +++++++ internal/vm/builder/vpmem_test.go | 32 +++++++++ internal/vm/builder/vsmb_test.go | 43 ++++++++++++ 10 files changed, 516 insertions(+) create mode 100644 internal/vm/builder/boot_test.go create mode 100644 internal/vm/builder/builder_test.go create mode 100644 internal/vm/builder/device_test.go create mode 100644 internal/vm/builder/memory_test.go create mode 100644 internal/vm/builder/numa_test.go create mode 100644 internal/vm/builder/processor_test.go create mode 100644 internal/vm/builder/scsi_test.go create mode 100644 internal/vm/builder/storage_test.go create mode 100644 internal/vm/builder/vpmem_test.go create mode 100644 internal/vm/builder/vsmb_test.go diff --git a/internal/vm/builder/boot_test.go b/internal/vm/builder/boot_test.go new file mode 100644 index 0000000000..7a09bbee22 --- /dev/null +++ b/internal/vm/builder/boot_test.go @@ -0,0 +1,64 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestBootConfig(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var mboot BootOptions = b + + bootEntry := &hcsschema.UefiBootEntry{ + DevicePath: "path", + DeviceType: "VmbFs", + VmbFsRootPath: "root", + OptionalData: "args", + } + mboot.SetUEFIBoot(bootEntry) + + if cs.VirtualMachine.Chipset.Uefi == nil || cs.VirtualMachine.Chipset.Uefi.BootThis == nil { + t.Fatal("UEFI boot not applied") + } + + got := cs.VirtualMachine.Chipset.Uefi.BootThis + if got.DevicePath != "path" { + t.Fatalf("UEFI DevicePath = %q, want %q", got.DevicePath, "path") + } + if got.DeviceType != "VmbFs" { + t.Fatalf("UEFI DeviceType = %q, want %q", got.DeviceType, "VmbFs") + } + if got.VmbFsRootPath != "root" { + t.Fatalf("UEFI VmbFsRootPath = %q, want %q", got.VmbFsRootPath, "root") + } + if got.OptionalData != "args" { + t.Fatalf("UEFI OptionalData = %q, want %q", got.OptionalData, "args") + } + + linuxBoot := &hcsschema.LinuxKernelDirect{ + KernelFilePath: "kernel", + InitRdPath: "initrd", + KernelCmdLine: "cmd", + } + err := mboot.SetLinuxKernelDirectBoot(linuxBoot) + if err != nil { + t.Fatalf("SetLinuxKernelDirectBoot error = %v", err) + } + if cs.VirtualMachine.Chipset.LinuxKernelDirect == nil { + t.Fatal("LinuxKernelDirect not applied") + } + lkd := cs.VirtualMachine.Chipset.LinuxKernelDirect + if lkd.KernelFilePath != "kernel" { + t.Fatalf("LinuxKernelDirect KernelFilePath = %q, want %q", lkd.KernelFilePath, "kernel") + } + if lkd.InitRdPath != "initrd" { + t.Fatalf("LinuxKernelDirect InitRdPath = %q, want %q", lkd.InitRdPath, "initrd") + } + if lkd.KernelCmdLine != "cmd" { + t.Fatalf("LinuxKernelDirect KernelCmdLine = %q, want %q", lkd.KernelCmdLine, "cmd") + } +} diff --git a/internal/vm/builder/builder_test.go b/internal/vm/builder/builder_test.go new file mode 100644 index 0000000000..5a826f77bc --- /dev/null +++ b/internal/vm/builder/builder_test.go @@ -0,0 +1,46 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" + + "github.com/pkg/errors" +) + +func newBuilder(t *testing.T, guestOS vm.GuestOS) (*UtilityVM, *hcsschema.ComputeSystem) { + t.Helper() + b, err := New("owner", guestOS) + if err != nil { + t.Fatalf("New() error = %v", err) + } + + return b, b.Get() +} + +func TestNewBuilder_InvalidGuestOS(t *testing.T) { + if _, err := New("owner", vm.GuestOS("unknown")); !errors.Is(err, errUnknownGuestOS) { + t.Fatalf("New() error = %v, want %v", err, errUnknownGuestOS) + } +} + +func TestNewBuilder_DefaultDevices(t *testing.T) { + _, cs := newBuilder(t, vm.Windows) + if cs.VirtualMachine.Devices.VirtualSmb == nil { + t.Fatal("VirtualSmb should be initialized for Windows") + } + if cs.VirtualMachine.Devices.Plan9 != nil { + t.Fatal("Plan9 should be nil for Windows") + } + + _, cs = newBuilder(t, vm.Linux) + if cs.VirtualMachine.Devices.Plan9 == nil { + t.Fatal("Plan9 should be initialized for Linux") + } + if cs.VirtualMachine.Devices.VirtualSmb != nil { + t.Fatal("VirtualSmb should be nil for Linux") + } +} diff --git a/internal/vm/builder/device_test.go b/internal/vm/builder/device_test.go new file mode 100644 index 0000000000..6cd06af75b --- /dev/null +++ b/internal/vm/builder/device_test.go @@ -0,0 +1,62 @@ +//go:build windows + +package builder + +import ( + "strconv" + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +func TestVPCIDevice(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var devices DeviceOptions = b + device := hcsschema.VirtualPciFunction{DeviceInstancePath: "PCI\\VEN_1234", VirtualFunction: 2} + + if err := devices.AddVPCIDevice(device, true); err != nil { + t.Fatalf("AddVPCIDevice error = %v", err) + } + if len(cs.VirtualMachine.Devices.VirtualPci) != 1 { + t.Fatalf("VirtualPci entries = %d, want 1", len(cs.VirtualMachine.Devices.VirtualPci)) + } + for _, entry := range cs.VirtualMachine.Devices.VirtualPci { + if len(entry.Functions) != 1 { + t.Fatalf("VirtualPci Functions = %d, want 1", len(entry.Functions)) + } + if entry.Functions[0].DeviceInstancePath != device.DeviceInstancePath || entry.Functions[0].VirtualFunction != device.VirtualFunction { + t.Fatal("VPCI function not applied as expected") + } + if entry.PropagateNumaAffinity == nil || !*entry.PropagateNumaAffinity { + t.Fatal("PropagateNumaAffinity should be true") + } + } + + if err := devices.AddVPCIDevice(device, false); !errors.Is(err, errAlreadySet) { + t.Fatalf("AddVPCIDevice duplicate error = %v, want %v", err, errAlreadySet) + } +} + +func TestSerialConsoleAndGraphics(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var devices DeviceOptions = b + if err := devices.SetSerialConsole(1, "not-a-pipe"); err == nil { + t.Fatal("SetSerialConsole should reject non-pipe path") + } + + pipePath := `\\.\pipe\serial` + if err := devices.SetSerialConsole(1, pipePath); err != nil { + t.Fatalf("SetSerialConsole error = %v", err) + } + key := strconv.Itoa(1) + if cs.VirtualMachine.Devices.ComPorts[key].NamedPipe != pipePath { + t.Fatal("serial console named pipe not set as expected") + } + + devices.EnableGraphicsConsole() + if cs.VirtualMachine.Devices.Keyboard == nil || cs.VirtualMachine.Devices.EnhancedModeVideo == nil || cs.VirtualMachine.Devices.VideoMonitor == nil { + t.Fatal("graphics console devices not enabled") + } +} diff --git a/internal/vm/builder/memory_test.go b/internal/vm/builder/memory_test.go new file mode 100644 index 0000000000..64ef17e1e3 --- /dev/null +++ b/internal/vm/builder/memory_test.go @@ -0,0 +1,86 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestMemoryConfig(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var memory MemoryOptions = b + + backingVirtual := hcsschema.MemoryBackingType_VIRTUAL + backingPhysical := hcsschema.MemoryBackingType_PHYSICAL + + memory.SetMemoryLimit(512) + memory.SetMemoryHints(&hcsschema.VirtualMachineMemory{ + Backing: &backingVirtual, + EnableDeferredCommit: true, + EnableHotHint: true, + EnableColdHint: false, + EnableColdDiscardHint: true, + }) + + mem := cs.VirtualMachine.ComputeTopology.Memory + if mem.SizeInMB != 512 { + t.Fatalf("SizeInMB = %d, want %d", mem.SizeInMB, 512) + } + if mem.Backing == nil || *mem.Backing != hcsschema.MemoryBackingType_VIRTUAL { + t.Fatal("Backing not set to VIRTUAL") + } + if !mem.AllowOvercommit || !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { + t.Fatal("memory hints not applied as expected") + } + + memory.SetMemoryHints(&hcsschema.VirtualMachineMemory{ + Backing: &backingPhysical, + EnableDeferredCommit: true, + EnableHotHint: true, + EnableColdHint: false, + EnableColdDiscardHint: true, + }) + + mem = cs.VirtualMachine.ComputeTopology.Memory + if mem.Backing == nil || *mem.Backing != hcsschema.MemoryBackingType_PHYSICAL { + t.Fatal("Backing not set to PHYSICAL") + } + if mem.AllowOvercommit || !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { + t.Fatal("memory hints not applied as expected") + } + + memory.SetMMIOConfig(64, 128, 256) + if mem.LowMMIOGapInMB != 64 || mem.HighMMIOBaseInMB != 128 || mem.HighMMIOGapInMB != 256 { + t.Fatal("MMIO config not applied as expected") + } + + memory.SetFirmwareFallbackMeasuredSlit() + if mem.SlitType == nil || *mem.SlitType != hcsschema.VirtualSlitType_FIRMWARE_FALLBACK_MEASURED { + t.Fatal("SlitType not set to FIRMWARE_FALLBACK_MEASURED") + } +} + +func TestMemoryHintsNilBacking(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var memory MemoryOptions = b + + defer func() { + if r := recover(); r != nil { + t.Fatalf("SetMemoryHints panicked: %v", r) + } + }() + + memory.SetMemoryHints(nil) + memory.SetMemoryHints(&hcsschema.VirtualMachineMemory{}) + + mem := cs.VirtualMachine.ComputeTopology.Memory + if mem.Backing != nil { + t.Fatal("Backing should remain nil when not provided") + } + if mem.AllowOvercommit { + t.Fatal("AllowOvercommit should remain false when Backing is nil") + } +} diff --git a/internal/vm/builder/numa_test.go b/internal/vm/builder/numa_test.go new file mode 100644 index 0000000000..986fdfbbb2 --- /dev/null +++ b/internal/vm/builder/numa_test.go @@ -0,0 +1,97 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestNUMASettings(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var numaManager NumaOptions = b + virtualNodeCount := uint8(2) + maxSizePerNode := uint64(4096) + numaProcessors := &hcsschema.NumaProcessors{ + CountPerNode: hcsschema.Range{Max: 4}, + NodePerSocket: 1, + } + numa := &hcsschema.Numa{ + VirtualNodeCount: virtualNodeCount, + PreferredPhysicalNodes: []int64{0, 1}, + Settings: []hcsschema.NumaSetting{ + { + VirtualNodeNumber: 0, + PhysicalNodeNumber: 0, + VirtualSocketNumber: 0, + CountOfProcessors: 2, + CountOfMemoryBlocks: 1024, + MemoryBackingType: hcsschema.MemoryBackingType_VIRTUAL, + }, + { + VirtualNodeNumber: 1, + PhysicalNodeNumber: 1, + VirtualSocketNumber: 1, + CountOfProcessors: 2, + CountOfMemoryBlocks: 1024, + MemoryBackingType: hcsschema.MemoryBackingType_PHYSICAL, + }, + }, + MaxSizePerNode: maxSizePerNode, + } + + numaManager.SetNUMAProcessorsSettings(numaProcessors) + numaManager.SetNUMASettings(numa) + + gotProcessors := cs.VirtualMachine.ComputeTopology.Processor.NumaProcessorsSettings + if gotProcessors == nil { + t.Fatal("NUMA processor settings not applied") + } + if gotProcessors.CountPerNode.Max != 4 { + t.Fatalf("CountPerNode.Max = %d, want %d", gotProcessors.CountPerNode.Max, 4) + } + if gotProcessors.NodePerSocket != 1 { + t.Fatalf("NodePerSocket = %d, want %d", gotProcessors.NodePerSocket, 1) + } + + gotNUMA := cs.VirtualMachine.ComputeTopology.Numa + if gotNUMA == nil { + t.Fatal("NUMA settings not applied") + } + if gotNUMA.VirtualNodeCount != virtualNodeCount { + t.Fatalf("VirtualNodeCount = %d, want %d", gotNUMA.VirtualNodeCount, virtualNodeCount) + } + if gotNUMA.MaxSizePerNode != maxSizePerNode { + t.Fatalf("MaxSizePerNode = %d, want %d", gotNUMA.MaxSizePerNode, maxSizePerNode) + } + if len(gotNUMA.PreferredPhysicalNodes) != 2 || gotNUMA.PreferredPhysicalNodes[0] != 0 || gotNUMA.PreferredPhysicalNodes[1] != 1 { + t.Fatalf("PreferredPhysicalNodes = %v, want [0 1]", gotNUMA.PreferredPhysicalNodes) + } + if len(gotNUMA.Settings) != 2 { + t.Fatalf("Settings length = %d, want %d", len(gotNUMA.Settings), 2) + } + + first := gotNUMA.Settings[0] + if first.VirtualNodeNumber != 0 || first.PhysicalNodeNumber != 0 || first.VirtualSocketNumber != 0 { + t.Fatal("first NUMA setting node/socket numbers not applied as expected") + } + if first.CountOfProcessors != 2 || first.CountOfMemoryBlocks != 1024 { + t.Fatal("first NUMA setting processor/memory counts not applied as expected") + } + if first.MemoryBackingType != hcsschema.MemoryBackingType_VIRTUAL { + t.Fatalf("first NUMA setting MemoryBackingType = %s, want %s", first.MemoryBackingType, hcsschema.MemoryBackingType_VIRTUAL) + } + + second := gotNUMA.Settings[1] + if second.VirtualNodeNumber != 1 || second.PhysicalNodeNumber != 1 || second.VirtualSocketNumber != 1 { + t.Fatal("second NUMA setting node/socket numbers not applied as expected") + } + if second.CountOfProcessors != 2 || second.CountOfMemoryBlocks != 1024 { + t.Fatal("second NUMA setting processor/memory counts not applied as expected") + } + if second.MemoryBackingType != hcsschema.MemoryBackingType_PHYSICAL { + t.Fatalf("second NUMA setting MemoryBackingType = %s, want %s", second.MemoryBackingType, hcsschema.MemoryBackingType_PHYSICAL) + } +} diff --git a/internal/vm/builder/processor_test.go b/internal/vm/builder/processor_test.go new file mode 100644 index 0000000000..7d05eec875 --- /dev/null +++ b/internal/vm/builder/processor_test.go @@ -0,0 +1,26 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestProcessorConfigAndCPUGroup(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var processor ProcessorOptions = b + + processor.SetProcessorLimits(&hcsschema.VirtualMachineProcessor{Count: 4, Limit: 2500, Weight: 200}) + proc := cs.VirtualMachine.ComputeTopology.Processor + if proc.Count != 4 || proc.Limit != 2500 || proc.Weight != 200 { + t.Fatal("processor config not applied as expected") + } + + processor.SetCPUGroup(&hcsschema.CpuGroup{Id: "cg1"}) + if proc.CpuGroup == nil || proc.CpuGroup.Id != "cg1" { + t.Fatal("cpu group not applied as expected") + } +} diff --git a/internal/vm/builder/scsi_test.go b/internal/vm/builder/scsi_test.go new file mode 100644 index 0000000000..80eaf59972 --- /dev/null +++ b/internal/vm/builder/scsi_test.go @@ -0,0 +1,34 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestSCSI(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var devices DeviceOptions = b + + if err := devices.AddSCSIDisk("0", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err == nil { + t.Fatal("AddSCSIDisk should fail when controller missing") + } + + devices.AddSCSIController("0") + if err := devices.AddSCSIDisk("0", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: true}); err != nil { + t.Fatalf("AddSCSIDisk error = %v", err) + } + + ctrl := cs.VirtualMachine.Devices.Scsi["0"] + att := ctrl.Attachments["1"] + if att.Path != "disk.vhdx" || att.Type_ != "VirtualDisk" || !att.ReadOnly { + t.Fatal("SCSI attachment not applied as expected") + } + + if err := devices.AddSCSIDisk("missing", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err == nil { + t.Fatal("AddSCSIDisk should fail when controller does not exist") + } +} diff --git a/internal/vm/builder/storage_test.go b/internal/vm/builder/storage_test.go new file mode 100644 index 0000000000..ca558e2d16 --- /dev/null +++ b/internal/vm/builder/storage_test.go @@ -0,0 +1,26 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestStorageQoS(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var storage StorageQoSOptions = b + + storage.SetStorageQoS(&hcsschema.StorageQoS{ + IopsMaximum: 1000, + BandwidthMaximum: 2000, + }) + if cs.VirtualMachine.StorageQoS == nil { + t.Fatal("StorageQoS should be initialized") + } + if cs.VirtualMachine.StorageQoS.IopsMaximum != 1000 || cs.VirtualMachine.StorageQoS.BandwidthMaximum != 2000 { + t.Fatal("StorageQoS not applied as expected") + } +} diff --git a/internal/vm/builder/vpmem_test.go b/internal/vm/builder/vpmem_test.go new file mode 100644 index 0000000000..cb418d554e --- /dev/null +++ b/internal/vm/builder/vpmem_test.go @@ -0,0 +1,32 @@ +//go:build windows + +package builder + +import ( + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestVPMem(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + var devices DeviceOptions = b + if err := devices.AddVPMemDevice("0", hcsschema.VirtualPMemDevice{HostPath: "pmem.img", ReadOnly: true, ImageFormat: "raw"}); err == nil { + t.Fatal("AddVPMemDevice should fail when controller missing") + } + + devices.AddVPMemController(2, 1024) + if err := devices.AddVPMemDevice("0", hcsschema.VirtualPMemDevice{HostPath: "pmem.img", ReadOnly: true, ImageFormat: "raw"}); err != nil { + t.Fatalf("AddVPMemDevice error = %v", err) + } + + controller := cs.VirtualMachine.Devices.VirtualPMem + if controller.MaximumCount != 2 || controller.MaximumSizeBytes != 1024 { + t.Fatal("VPMem controller not applied as expected") + } + device := controller.Devices["0"] + if device.HostPath != "pmem.img" || !device.ReadOnly || device.ImageFormat != "raw" { + t.Fatal("VPMem device not applied as expected") + } +} diff --git a/internal/vm/builder/vsmb_test.go b/internal/vm/builder/vsmb_test.go new file mode 100644 index 0000000000..1ea70b296f --- /dev/null +++ b/internal/vm/builder/vsmb_test.go @@ -0,0 +1,43 @@ +//go:build windows + +package builder + +import ( + "reflect" + "testing" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestVSMB(t *testing.T) { + b, cs := newBuilder(t, vm.Windows) + var devices DeviceOptions = b + opts := &hcsschema.VirtualSmbShareOptions{ReadOnly: true} + share1 := hcsschema.VirtualSmbShare{ + Name: "data", + Path: "C:\\share", + AllowedFiles: []string{"a.txt"}, + Options: opts, + } + share2 := hcsschema.VirtualSmbShare{ + Name: "data2", + Path: "C:\\share2", + AllowedFiles: []string{"b.txt"}, + Options: opts, + } + if err := devices.AddVSMBShare(share1); err != nil { + t.Fatalf("AddVSMBShare error = %v", err) + } + if err := devices.AddVSMBShare(share2); err != nil { + t.Fatalf("AddVSMBShare error = %v", err) + } + + vsmb := cs.VirtualMachine.Devices.VirtualSmb + if vsmb == nil || len(vsmb.Shares) != 2 { + t.Fatal("VSMB not configured as expected") + } + if !reflect.DeepEqual(vsmb.Shares[0], share1) || !reflect.DeepEqual(vsmb.Shares[1], share2) { + t.Fatal("VSMB shares not applied as expected") + } +} From 56488775850ba3900d669c7dc2017de80d11269d Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Fri, 20 Feb 2026 03:25:20 +0530 Subject: [PATCH 3/6] review comments Signed-off-by: Harsh Rawat --- internal/vm/builder/device.go | 17 ++++++----- internal/vm/builder/device_test.go | 49 ++++++++++++++++++++++-------- internal/vm/builder/scsi.go | 1 - internal/vm/builder/scsi_test.go | 18 +++++++++-- internal/vm/builder/vsmb.go | 10 ++++-- internal/vm/builder/vsmb_test.go | 6 ++++ internal/vm/vmmanager/scsi.go | 9 ++---- internal/vm/vmmanager/vmsocket.go | 13 -------- 8 files changed, 77 insertions(+), 46 deletions(-) diff --git a/internal/vm/builder/device.go b/internal/vm/builder/device.go index 03bb23af3f..d2b1a4af07 100644 --- a/internal/vm/builder/device.go +++ b/internal/vm/builder/device.go @@ -28,7 +28,7 @@ type vPCIDevice struct { type DeviceOptions interface { // AddVPCIDevice adds a PCI device to the Utility VM. // If the device is already added, we return an error. - AddVPCIDevice(device hcsschema.VirtualPciFunction, numaAffinity bool) error + AddVPCIDevice(vmbusGUID guid.GUID, device hcsschema.VirtualPciFunction, numaAffinity bool) error // AddSCSIController adds a SCSI controller to the Utility VM with the specified ID. AddSCSIController(id string) // AddSCSIDisk adds a SCSI disk to the Utility VM under the specified controller and LUN. @@ -37,6 +37,8 @@ type DeviceOptions interface { AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) // AddVPMemDevice adds a VPMem device to the Utility VM under the VPMem controller. AddVPMemDevice(id string, device hcsschema.VirtualPMemDevice) error + // AddVSMB initializes the VSMB settings for the Utility VM. + AddVSMB(settings hcsschema.VirtualSmb) // AddVSMBShare adds a VSMB share to the Utility VM. AddVSMBShare(share hcsschema.VirtualSmbShare) error // SetSerialConsole sets up a serial console for `port`. Output will be relayed to the listener specified @@ -48,17 +50,12 @@ type DeviceOptions interface { var _ DeviceOptions = (*UtilityVM)(nil) -func (uvmb *UtilityVM) AddVPCIDevice(device hcsschema.VirtualPciFunction, numaAffinity bool) error { +func (uvmb *UtilityVM) AddVPCIDevice(vmbusGUID guid.GUID, device hcsschema.VirtualPciFunction, numaAffinity bool) error { _, ok := uvmb.assignedDevices[device] if ok { return errors.Wrapf(errAlreadySet, "device %v already assigned to utility VM", device) } - vmbusGUID, err := guid.NewV4() - if err != nil { - return errors.Wrap(err, "failed to generate VMBus GUID for device") - } - var propagateAffinity *bool if numaAffinity { propagateAffinity = &numaAffinity @@ -85,8 +82,12 @@ func (uvmb *UtilityVM) AddVPCIDevice(device hcsschema.VirtualPciFunction, numaAf return nil } +const ( + pipePrefix = `\\.\pipe\` +) + func (uvmb *UtilityVM) SetSerialConsole(port uint32, listenerPath string) error { - if !strings.HasPrefix(listenerPath, `\\.\pipe\`) { + if !strings.HasPrefix(listenerPath, pipePrefix) { return errors.New("listener for serial console is not a named pipe") } diff --git a/internal/vm/builder/device_test.go b/internal/vm/builder/device_test.go index 6cd06af75b..461aa3cedc 100644 --- a/internal/vm/builder/device_test.go +++ b/internal/vm/builder/device_test.go @@ -6,35 +6,52 @@ import ( "strconv" "testing" + "github.com/Microsoft/go-winio/pkg/guid" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/vm" "github.com/pkg/errors" ) +// administratorsPipePrefix is the protected pipe prefix for administrators. +// It is also covered by pipePrefix since it starts with `\\.\pipe\`. +const administratorsPipePrefix = `\\.\pipe\ProtectedPrefix\Administrators\` + func TestVPCIDevice(t *testing.T) { b, cs := newBuilder(t, vm.Linux) var devices DeviceOptions = b device := hcsschema.VirtualPciFunction{DeviceInstancePath: "PCI\\VEN_1234", VirtualFunction: 2} - if err := devices.AddVPCIDevice(device, true); err != nil { + vmbusGUID, err := guid.NewV4() + if err != nil { + t.Fatalf("guid.NewV4 error = %v", err) + } + + if err := devices.AddVPCIDevice(vmbusGUID, device, true); err != nil { t.Fatalf("AddVPCIDevice error = %v", err) } if len(cs.VirtualMachine.Devices.VirtualPci) != 1 { t.Fatalf("VirtualPci entries = %d, want 1", len(cs.VirtualMachine.Devices.VirtualPci)) } - for _, entry := range cs.VirtualMachine.Devices.VirtualPci { - if len(entry.Functions) != 1 { - t.Fatalf("VirtualPci Functions = %d, want 1", len(entry.Functions)) - } - if entry.Functions[0].DeviceInstancePath != device.DeviceInstancePath || entry.Functions[0].VirtualFunction != device.VirtualFunction { - t.Fatal("VPCI function not applied as expected") - } - if entry.PropagateNumaAffinity == nil || !*entry.PropagateNumaAffinity { - t.Fatal("PropagateNumaAffinity should be true") - } + entry, ok := cs.VirtualMachine.Devices.VirtualPci[vmbusGUID.String()] + if !ok { + t.Fatal("VirtualPci entry not found for provided vmbusGUID") + } + if len(entry.Functions) != 1 { + t.Fatalf("VirtualPci Functions = %d, want 1", len(entry.Functions)) + } + if entry.Functions[0].DeviceInstancePath != device.DeviceInstancePath || entry.Functions[0].VirtualFunction != device.VirtualFunction { + t.Fatal("VPCI function not applied as expected") + } + if entry.PropagateNumaAffinity == nil || !*entry.PropagateNumaAffinity { + t.Fatal("PropagateNumaAffinity should be true") } - if err := devices.AddVPCIDevice(device, false); !errors.Is(err, errAlreadySet) { + dupGUID, err := guid.NewV4() + if err != nil { + t.Fatalf("guid.NewV4 error = %v", err) + } + + if err := devices.AddVPCIDevice(dupGUID, device, false); !errors.Is(err, errAlreadySet) { t.Fatalf("AddVPCIDevice duplicate error = %v, want %v", err, errAlreadySet) } } @@ -55,6 +72,14 @@ func TestSerialConsoleAndGraphics(t *testing.T) { t.Fatal("serial console named pipe not set as expected") } + adminPipePath := administratorsPipePrefix + "serial" + if err := devices.SetSerialConsole(1, adminPipePath); err != nil { + t.Fatalf("SetSerialConsole should accept administrators pipe prefix, error = %v", err) + } + if cs.VirtualMachine.Devices.ComPorts[key].NamedPipe != adminPipePath { + t.Fatal("serial console administrators named pipe not set as expected") + } + devices.EnableGraphicsConsole() if cs.VirtualMachine.Devices.Keyboard == nil || cs.VirtualMachine.Devices.EnhancedModeVideo == nil || cs.VirtualMachine.Devices.VideoMonitor == nil { t.Fatal("graphics console devices not enabled") diff --git a/internal/vm/builder/scsi.go b/internal/vm/builder/scsi.go index 270a56a7b1..5b9d439849 100644 --- a/internal/vm/builder/scsi.go +++ b/internal/vm/builder/scsi.go @@ -27,7 +27,6 @@ func (uvmb *UtilityVM) AddSCSIDisk(controller string, lun string, disk hcsschema } ctrl.Attachments[lun] = disk - uvmb.doc.VirtualMachine.Devices.Scsi[controller] = ctrl return nil } diff --git a/internal/vm/builder/scsi_test.go b/internal/vm/builder/scsi_test.go index 80eaf59972..a2b9c6a658 100644 --- a/internal/vm/builder/scsi_test.go +++ b/internal/vm/builder/scsi_test.go @@ -22,12 +22,26 @@ func TestSCSI(t *testing.T) { t.Fatalf("AddSCSIDisk error = %v", err) } - ctrl := cs.VirtualMachine.Devices.Scsi["0"] - att := ctrl.Attachments["1"] + // Verify the attachment is reflected directly in the document (map reference semantics - + // no write-back of the Scsi struct copy is needed). + att := cs.VirtualMachine.Devices.Scsi["0"].Attachments["1"] if att.Path != "disk.vhdx" || att.Type_ != "VirtualDisk" || !att.ReadOnly { t.Fatal("SCSI attachment not applied as expected") } + // Add a second disk and confirm both attachments are present in the document, + // verifying that multiple calls also work correctly. + if err := devices.AddSCSIDisk("0", "2", hcsschema.Attachment{Path: "disk2.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err != nil { + t.Fatalf("AddSCSIDisk (lun 2) error = %v", err) + } + if len(cs.VirtualMachine.Devices.Scsi["0"].Attachments) != 2 { + t.Fatalf("expected 2 attachments, got %d", len(cs.VirtualMachine.Devices.Scsi["0"].Attachments)) + } + att2 := cs.VirtualMachine.Devices.Scsi["0"].Attachments["2"] + if att2.Path != "disk2.vhdx" || att2.ReadOnly { + t.Fatal("second SCSI attachment not applied as expected") + } + if err := devices.AddSCSIDisk("missing", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err == nil { t.Fatal("AddSCSIDisk should fail when controller does not exist") } diff --git a/internal/vm/builder/vsmb.go b/internal/vm/builder/vsmb.go index 6f3132db4c..5292bf3c7e 100644 --- a/internal/vm/builder/vsmb.go +++ b/internal/vm/builder/vsmb.go @@ -4,13 +4,17 @@ package builder import ( hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + + "github.com/pkg/errors" ) +func (uvmb *UtilityVM) AddVSMB(settings hcsschema.VirtualSmb) { + uvmb.doc.VirtualMachine.Devices.VirtualSmb = &settings +} + func (uvmb *UtilityVM) AddVSMBShare(share hcsschema.VirtualSmbShare) error { if uvmb.doc.VirtualMachine.Devices.VirtualSmb == nil { - uvmb.doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{ - DirectFileMappingInMB: 1024, // Sensible default, but could be a tuning parameter somewhere - } + return errors.New("VSMB has not been added") } uvmb.doc.VirtualMachine.Devices.VirtualSmb.Shares = append( diff --git a/internal/vm/builder/vsmb_test.go b/internal/vm/builder/vsmb_test.go index 1ea70b296f..dd800c47b9 100644 --- a/internal/vm/builder/vsmb_test.go +++ b/internal/vm/builder/vsmb_test.go @@ -26,6 +26,9 @@ func TestVSMB(t *testing.T) { AllowedFiles: []string{"b.txt"}, Options: opts, } + + devices.AddVSMB(hcsschema.VirtualSmb{DirectFileMappingInMB: 1024}) + if err := devices.AddVSMBShare(share1); err != nil { t.Fatalf("AddVSMBShare error = %v", err) } @@ -37,6 +40,9 @@ func TestVSMB(t *testing.T) { if vsmb == nil || len(vsmb.Shares) != 2 { t.Fatal("VSMB not configured as expected") } + if vsmb.DirectFileMappingInMB != 1024 { + t.Fatalf("DirectFileMappingInMB = %d, want 1024", vsmb.DirectFileMappingInMB) + } if !reflect.DeepEqual(vsmb.Shares[0], share1) || !reflect.DeepEqual(vsmb.Shares[1], share2) { t.Fatal("VSMB shares not applied as expected") } diff --git a/internal/vm/vmmanager/scsi.go b/internal/vm/vmmanager/scsi.go index 67c1916714..7b294ddb6d 100644 --- a/internal/vm/vmmanager/scsi.go +++ b/internal/vm/vmmanager/scsi.go @@ -25,13 +25,8 @@ 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, - Settings: hcsschema.Attachment{ - Path: disk.Path, - Type_: disk.Type_, - ReadOnly: disk.ReadOnly, - ExtensibleVirtualDiskType: disk.ExtensibleVirtualDiskType, - }, + RequestType: guestrequest.RequestTypeAdd, + Settings: disk, ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, guestrequest.ScsiControllerGuids[controller], lun), } if err := uvm.cs.Modify(ctx, request); err != nil { diff --git a/internal/vm/vmmanager/vmsocket.go b/internal/vm/vmmanager/vmsocket.go index 746a9ec72c..e1b6159af8 100644 --- a/internal/vm/vmmanager/vmsocket.go +++ b/internal/vm/vmmanager/vmsocket.go @@ -5,23 +5,17 @@ package vmmanager import ( "context" "fmt" - "net" "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" - "github.com/Microsoft/go-winio" - "github.com/Microsoft/go-winio/pkg/guid" "github.com/pkg/errors" ) // VMSocketManager manages configuration for a hypervisor socket transport. This includes sockets such as // HvSocket and Vsock. type VMSocketManager interface { - // VMSocketListen will create the requested HvSocket and listen on the address specified by `connID`. - VMSocketListen(connID guid.GUID) (net.Listener, error) - // UpdateHvSocketService will update the configuration for the HvSocket service with the specified `serviceID`. UpdateHvSocketService(ctx context.Context, serviceID string, config *hcsschema.HvSocketServiceConfig) error @@ -31,13 +25,6 @@ type VMSocketManager interface { var _ VMSocketManager = (*UtilityVM)(nil) -func (uvm *UtilityVM) VMSocketListen(connID guid.GUID) (net.Listener, error) { - return winio.ListenHvsock(&winio.HvsockAddr{ - VMID: uvm.vmID, - ServiceID: connID, - }) -} - // UpdateHvSocketService calls HCS to update/create the hvsocket service for // the UVM. Takes in a service ID and the hvsocket service configuration. If there is no // entry for the service ID already it will be created. The same call on HvSockets side From 925f5eabe19d0646884f16dff2ad25ed306f775e Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Sat, 21 Feb 2026 04:42:47 +0530 Subject: [PATCH 4/6] review comments Signed-off-by: Harsh Rawat --- internal/vm/README.md | 4 +- internal/vm/builder/boot_test.go | 3 +- internal/vm/builder/builder.go | 21 +--- internal/vm/builder/builder_test.go | 34 ++---- internal/vm/builder/device.go | 14 ++- internal/vm/builder/device_test.go | 44 ++++++- internal/vm/builder/memory_test.go | 5 +- internal/vm/builder/numa_test.go | 3 +- internal/vm/builder/processor.go | 13 +- internal/vm/builder/processor_test.go | 166 ++++++++++++++++++++++++-- internal/vm/builder/scsi_test.go | 3 +- internal/vm/builder/storage_test.go | 3 +- internal/vm/builder/vpmem_test.go | 3 +- internal/vm/builder/vsmb_test.go | 3 +- internal/vm/vm.go | 11 -- internal/vm/vmmanager/lifetime.go | 9 -- internal/vm/vmmanager/pci.go | 20 +--- internal/vm/vmmanager/uvm.go | 11 +- 18 files changed, 241 insertions(+), 129 deletions(-) delete mode 100644 internal/vm/vm.go diff --git a/internal/vm/README.md b/internal/vm/README.md index ecab131ad2..2606db0010 100644 --- a/internal/vm/README.md +++ b/internal/vm/README.md @@ -11,8 +11,6 @@ configuration, host-side management, and guest-side actions distinct so each lay ## Packages and Responsibilities -- `internal/vm` - - Shared types used across layers (for example, `GuestOS`, `SCSIDisk`). - `internal/vm/builder` - Interface definitions for shaping the VM configuration (`Builder` interface). - Concrete implementation of `Builder` for building `hcsschema.ComputeSystem` documents. @@ -38,7 +36,7 @@ configuration, host-side management, and guest-side actions distinct so each lay ## Example (High Level) ``` -builder, _ := builder.New("owner", vm.Linux) +builder, _ := builder.New("owner") // Configure the VM document. builder.Memory().SetMemoryLimit(1024) diff --git a/internal/vm/builder/boot_test.go b/internal/vm/builder/boot_test.go index 7a09bbee22..bfac2d5cfc 100644 --- a/internal/vm/builder/boot_test.go +++ b/internal/vm/builder/boot_test.go @@ -6,11 +6,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestBootConfig(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var mboot BootOptions = b bootEntry := &hcsschema.UefiBootEntry{ diff --git a/internal/vm/builder/builder.go b/internal/vm/builder/builder.go index 57a9cc7bda..79a383920f 100644 --- a/internal/vm/builder/builder.go +++ b/internal/vm/builder/builder.go @@ -5,21 +5,18 @@ package builder import ( hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/schemaversion" - "github.com/Microsoft/hcsshim/internal/vm" "github.com/pkg/errors" ) var ( - errAlreadySet = errors.New("field has already been set") - errUnknownGuestOS = errors.New("unknown guest operating system supplied") + errAlreadySet = errors.New("field has already been set") ) // UtilityVM is used to build a schema document for creating a Utility VM. // It provides methods for configuring various aspects of the Utility VM // such as memory, processors, devices, boot options, and storage QoS settings. type UtilityVM struct { - guestOS vm.GuestOS doc *hcsschema.ComputeSystem assignedDevices map[hcsschema.VirtualPciFunction]*vPCIDevice } @@ -27,7 +24,7 @@ type UtilityVM struct { // New returns the concrete builder, and callers are expected to use the // interface views (for example, NumaOptions, MemoryOptions) as needed. // This follows the "accept interfaces, return structs" convention. -func New(owner string, guestOS vm.GuestOS) (*UtilityVM, error) { +func New(owner string) (*UtilityVM, error) { doc := &hcsschema.ComputeSystem{ Owner: owner, SchemaVersion: schemaversion.SchemaV21(), @@ -54,26 +51,12 @@ func New(owner string, guestOS vm.GuestOS) (*UtilityVM, error) { }, } - switch guestOS { - case vm.Windows: - doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{} - case vm.Linux: - doc.VirtualMachine.Devices.Plan9 = &hcsschema.Plan9{} - default: - return nil, errUnknownGuestOS - } - return &UtilityVM{ - guestOS: guestOS, doc: doc, assignedDevices: make(map[hcsschema.VirtualPciFunction]*vPCIDevice), }, nil } -func (uvmb *UtilityVM) GuestOS() vm.GuestOS { - return uvmb.guestOS -} - func (uvmb *UtilityVM) Get() *hcsschema.ComputeSystem { return uvmb.doc } diff --git a/internal/vm/builder/builder_test.go b/internal/vm/builder/builder_test.go index 5a826f77bc..0509c5ccd3 100644 --- a/internal/vm/builder/builder_test.go +++ b/internal/vm/builder/builder_test.go @@ -6,14 +6,11 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" - - "github.com/pkg/errors" ) -func newBuilder(t *testing.T, guestOS vm.GuestOS) (*UtilityVM, *hcsschema.ComputeSystem) { +func newBuilder(t *testing.T) (*UtilityVM, *hcsschema.ComputeSystem) { t.Helper() - b, err := New("owner", guestOS) + b, err := New("owner") if err != nil { t.Fatalf("New() error = %v", err) } @@ -21,26 +18,15 @@ func newBuilder(t *testing.T, guestOS vm.GuestOS) (*UtilityVM, *hcsschema.Comput return b, b.Get() } -func TestNewBuilder_InvalidGuestOS(t *testing.T) { - if _, err := New("owner", vm.GuestOS("unknown")); !errors.Is(err, errUnknownGuestOS) { - t.Fatalf("New() error = %v, want %v", err, errUnknownGuestOS) - } -} - -func TestNewBuilder_DefaultDevices(t *testing.T) { - _, cs := newBuilder(t, vm.Windows) - if cs.VirtualMachine.Devices.VirtualSmb == nil { - t.Fatal("VirtualSmb should be initialized for Windows") - } - if cs.VirtualMachine.Devices.Plan9 != nil { - t.Fatal("Plan9 should be nil for Windows") +func TestNewBuilder_DefaultFields(t *testing.T) { + _, cs := newBuilder(t) + if cs.VirtualMachine == nil { + t.Fatal("VirtualMachine should be initialized") } - - _, cs = newBuilder(t, vm.Linux) - if cs.VirtualMachine.Devices.Plan9 == nil { - t.Fatal("Plan9 should be initialized for Linux") + if cs.VirtualMachine.Devices == nil { + t.Fatal("Devices should be initialized") } - if cs.VirtualMachine.Devices.VirtualSmb != nil { - t.Fatal("VirtualSmb should be nil for Linux") + if cs.VirtualMachine.Devices.HvSocket == nil { + t.Fatal("HvSocket should be initialized") } } diff --git a/internal/vm/builder/device.go b/internal/vm/builder/device.go index d2b1a4af07..5e131e05da 100644 --- a/internal/vm/builder/device.go +++ b/internal/vm/builder/device.go @@ -12,6 +12,10 @@ import ( "github.com/pkg/errors" ) +const ( + pipePrefix = `\\.\pipe\` +) + // vPCIDevice represents a vpci device. type vPCIDevice struct { // vmbusGUID is the instance ID for this device when it is exposed via VMBus @@ -41,6 +45,8 @@ type DeviceOptions interface { AddVSMB(settings hcsschema.VirtualSmb) // AddVSMBShare adds a VSMB share to the Utility VM. AddVSMBShare(share hcsschema.VirtualSmbShare) error + // AddPlan9 adds a Plan9 device to the Utility VM with the specified settings. + AddPlan9(settings *hcsschema.Plan9) // SetSerialConsole sets up a serial console for `port`. Output will be relayed to the listener specified // by `listenerPath`. For HCS `listenerPath` this is expected to be a path to a named pipe. SetSerialConsole(port uint32, listenerPath string) error @@ -82,10 +88,6 @@ func (uvmb *UtilityVM) AddVPCIDevice(vmbusGUID guid.GUID, device hcsschema.Virtu return nil } -const ( - pipePrefix = `\\.\pipe\` -) - func (uvmb *UtilityVM) SetSerialConsole(port uint32, listenerPath string) error { if !strings.HasPrefix(listenerPath, pipePrefix) { return errors.New("listener for serial console is not a named pipe") @@ -104,3 +106,7 @@ func (uvmb *UtilityVM) EnableGraphicsConsole() { uvmb.doc.VirtualMachine.Devices.EnhancedModeVideo = &hcsschema.EnhancedModeVideo{} uvmb.doc.VirtualMachine.Devices.VideoMonitor = &hcsschema.VideoMonitor{} } + +func (uvmb *UtilityVM) AddPlan9(settings *hcsschema.Plan9) { + uvmb.doc.VirtualMachine.Devices.Plan9 = settings +} diff --git a/internal/vm/builder/device_test.go b/internal/vm/builder/device_test.go index 461aa3cedc..e135131687 100644 --- a/internal/vm/builder/device_test.go +++ b/internal/vm/builder/device_test.go @@ -8,7 +8,6 @@ import ( "github.com/Microsoft/go-winio/pkg/guid" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" "github.com/pkg/errors" ) @@ -17,7 +16,7 @@ import ( const administratorsPipePrefix = `\\.\pipe\ProtectedPrefix\Administrators\` func TestVPCIDevice(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var devices DeviceOptions = b device := hcsschema.VirtualPciFunction{DeviceInstancePath: "PCI\\VEN_1234", VirtualFunction: 2} @@ -57,7 +56,7 @@ func TestVPCIDevice(t *testing.T) { } func TestSerialConsoleAndGraphics(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var devices DeviceOptions = b if err := devices.SetSerialConsole(1, "not-a-pipe"); err == nil { t.Fatal("SetSerialConsole should reject non-pipe path") @@ -85,3 +84,42 @@ func TestSerialConsoleAndGraphics(t *testing.T) { t.Fatal("graphics console devices not enabled") } } + +func TestAddPlan9(t *testing.T) { + b, cs := newBuilder(t) + var devices DeviceOptions = b + + share := hcsschema.Plan9Share{ + Name: "data", + AccessName: "data", + Path: "/host/path", + Port: 564, + ReadOnly: true, + } + settings := &hcsschema.Plan9{Shares: []hcsschema.Plan9Share{share}} + devices.AddPlan9(settings) + + if cs.VirtualMachine.Devices.Plan9 == nil { + t.Fatal("Plan9 should be set") + } + if len(cs.VirtualMachine.Devices.Plan9.Shares) != 1 { + t.Fatalf("Plan9 Shares = %d, want 1", len(cs.VirtualMachine.Devices.Plan9.Shares)) + } + got := cs.VirtualMachine.Devices.Plan9.Shares[0] + if got.Name != share.Name || got.AccessName != share.AccessName || got.Path != share.Path || got.Port != share.Port || got.ReadOnly != share.ReadOnly { + t.Fatalf("Plan9 share not applied as expected: got %+v, want %+v", got, share) + } +} + +func TestAddPlan9_Nil(t *testing.T) { + b, cs := newBuilder(t) + var devices DeviceOptions = b + + // First set a Plan9 config, then overwrite with nil. + devices.AddPlan9(&hcsschema.Plan9{Shares: []hcsschema.Plan9Share{{Name: "tmp"}}}) + devices.AddPlan9(nil) + + if cs.VirtualMachine.Devices.Plan9 != nil { + t.Fatal("Plan9 should be nil after AddPlan9(nil)") + } +} diff --git a/internal/vm/builder/memory_test.go b/internal/vm/builder/memory_test.go index 64ef17e1e3..e48ea8d33d 100644 --- a/internal/vm/builder/memory_test.go +++ b/internal/vm/builder/memory_test.go @@ -6,11 +6,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestMemoryConfig(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var memory MemoryOptions = b backingVirtual := hcsschema.MemoryBackingType_VIRTUAL @@ -64,7 +63,7 @@ func TestMemoryConfig(t *testing.T) { } func TestMemoryHintsNilBacking(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var memory MemoryOptions = b defer func() { diff --git a/internal/vm/builder/numa_test.go b/internal/vm/builder/numa_test.go index 986fdfbbb2..f96993d0ae 100644 --- a/internal/vm/builder/numa_test.go +++ b/internal/vm/builder/numa_test.go @@ -6,11 +6,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestNUMASettings(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var numaManager NumaOptions = b virtualNodeCount := uint8(2) maxSizePerNode := uint64(4096) diff --git a/internal/vm/builder/processor.go b/internal/vm/builder/processor.go index 57b5103e99..fac0d460e1 100644 --- a/internal/vm/builder/processor.go +++ b/internal/vm/builder/processor.go @@ -8,19 +8,18 @@ import ( // ProcessorOptions configures processor settings for the Utility VM. type ProcessorOptions interface { - // SetProcessorLimits applies Count, Limit, and Weight from the provided config. - SetProcessorLimits(config *hcsschema.VirtualMachineProcessor) + // SetProcessor sets processor related options for the Utility VM + SetProcessor(config *hcsschema.VirtualMachineProcessor) // SetCPUGroup sets the CPU group that the Utility VM will belong to on a Windows host. SetCPUGroup(cpuGroup *hcsschema.CpuGroup) } var _ ProcessorOptions = (*UtilityVM)(nil) -func (uvmb *UtilityVM) SetProcessorLimits(config *hcsschema.VirtualMachineProcessor) { - processor := uvmb.doc.VirtualMachine.ComputeTopology.Processor - processor.Count = config.Count - processor.Limit = config.Limit - processor.Weight = config.Weight +func (uvmb *UtilityVM) SetProcessor(config *hcsschema.VirtualMachineProcessor) { + if config != nil { + uvmb.doc.VirtualMachine.ComputeTopology.Processor = config + } } func (uvmb *UtilityVM) SetCPUGroup(cpuGroup *hcsschema.CpuGroup) { diff --git a/internal/vm/builder/processor_test.go b/internal/vm/builder/processor_test.go index 7d05eec875..34e5a5c938 100644 --- a/internal/vm/builder/processor_test.go +++ b/internal/vm/builder/processor_test.go @@ -6,21 +6,165 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) -func TestProcessorConfigAndCPUGroup(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) - var processor ProcessorOptions = b +func TestSetProcessor(t *testing.T) { + tests := []struct { + name string + // configs is a sequence of SetProcessor calls to apply. + configs []*hcsschema.VirtualMachineProcessor + // wantNoop when true means the final Processor should be the default (non-nil, zero-value). + wantNoop bool + // want is the expected final Processor state (ignored when wantNoop is true). + want *hcsschema.VirtualMachineProcessor + }{ + { + name: "all fields", + configs: []*hcsschema.VirtualMachineProcessor{ + {Count: 4, Limit: 2500, Weight: 200, Reservation: 1000}, + }, + want: &hcsschema.VirtualMachineProcessor{Count: 4, Limit: 2500, Weight: 200, Reservation: 1000}, + }, + { + name: "nil is no-op", + configs: []*hcsschema.VirtualMachineProcessor{nil}, + wantNoop: true, + }, + { + name: "overwrite replaces completely", + configs: []*hcsschema.VirtualMachineProcessor{ + {Count: 2, Weight: 100}, + {Count: 8, Limit: 5000}, + }, + want: &hcsschema.VirtualMachineProcessor{Count: 8, Limit: 5000}, + }, + { + name: "nil after set is no-op", + configs: []*hcsschema.VirtualMachineProcessor{ + {Count: 4, Weight: 300}, + nil, + }, + want: &hcsschema.VirtualMachineProcessor{Count: 4, Weight: 300}, + }, + { + name: "count only", + configs: []*hcsschema.VirtualMachineProcessor{ + {Count: 16}, + }, + want: &hcsschema.VirtualMachineProcessor{Count: 16}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, cs := newBuilder(t) + var processor ProcessorOptions = b + + for _, cfg := range tt.configs { + processor.SetProcessor(cfg) + } + + proc := cs.VirtualMachine.ComputeTopology.Processor + if proc == nil { + t.Fatal("Processor should never be nil") + } + + if tt.wantNoop { + // Default processor from New() is a zero-value struct. + if proc.Count != 0 || proc.Limit != 0 || proc.Weight != 0 || proc.Reservation != 0 { + t.Fatalf("expected default processor, got %+v", proc) + } + return + } + + if proc.Count != tt.want.Count { + t.Fatalf("Processor.Count = %d, want %d", proc.Count, tt.want.Count) + } + if proc.Limit != tt.want.Limit { + t.Fatalf("Processor.Limit = %d, want %d", proc.Limit, tt.want.Limit) + } + if proc.Weight != tt.want.Weight { + t.Fatalf("Processor.Weight = %d, want %d", proc.Weight, tt.want.Weight) + } + if proc.Reservation != tt.want.Reservation { + t.Fatalf("Processor.Reservation = %d, want %d", proc.Reservation, tt.want.Reservation) + } + }) + } +} - processor.SetProcessorLimits(&hcsschema.VirtualMachineProcessor{Count: 4, Limit: 2500, Weight: 200}) - proc := cs.VirtualMachine.ComputeTopology.Processor - if proc.Count != 4 || proc.Limit != 2500 || proc.Weight != 200 { - t.Fatal("processor config not applied as expected") +func TestSetCPUGroup(t *testing.T) { + tests := []struct { + name string + // processor is an optional SetProcessor call before SetCPUGroup. + // nil means use the default processor from New(). + processor *hcsschema.VirtualMachineProcessor + // groups is a sequence of SetCPUGroup calls to apply. + groups []*hcsschema.CpuGroup + // wantGroupID is the expected CpuGroup.Id after all calls. Empty means CpuGroup should be nil. + wantGroupID string + // wantCount is the expected Processor.Count after all calls (to verify SetCPUGroup doesn't clobber processor config). + wantCount uint32 + }{ + { + name: "set group on default processor", + groups: []*hcsschema.CpuGroup{{Id: "group-1"}}, + wantGroupID: "group-1", + }, + { + name: "nil clears group", + groups: []*hcsschema.CpuGroup{{Id: "group-1"}, nil}, + wantGroupID: "", + }, + { + name: "overwrite group", + groups: []*hcsschema.CpuGroup{{Id: "first"}, {Id: "second"}}, + wantGroupID: "second", + }, + { + name: "after SetProcessor", + processor: &hcsschema.VirtualMachineProcessor{Count: 4, Limit: 2500}, + groups: []*hcsschema.CpuGroup{{Id: "cg-after-set"}}, + wantGroupID: "cg-after-set", + wantCount: 4, + }, + { + name: "after nil SetProcessor (no-op)", + groups: []*hcsschema.CpuGroup{{Id: "safe-group"}}, + wantGroupID: "safe-group", + }, } - processor.SetCPUGroup(&hcsschema.CpuGroup{Id: "cg1"}) - if proc.CpuGroup == nil || proc.CpuGroup.Id != "cg1" { - t.Fatal("cpu group not applied as expected") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, cs := newBuilder(t) + var p ProcessorOptions = b + + if tt.processor != nil { + p.SetProcessor(tt.processor) + } + + for _, g := range tt.groups { + p.SetCPUGroup(g) + } + + proc := cs.VirtualMachine.ComputeTopology.Processor + if tt.wantGroupID == "" { + if proc.CpuGroup != nil { + t.Fatalf("CpuGroup = %+v, want nil", proc.CpuGroup) + } + } else { + if proc.CpuGroup == nil { + t.Fatal("CpuGroup should not be nil") + } + if proc.CpuGroup.Id != tt.wantGroupID { + t.Fatalf("CpuGroup.Id = %q, want %q", proc.CpuGroup.Id, tt.wantGroupID) + } + } + + if tt.wantCount != 0 && proc.Count != tt.wantCount { + t.Fatalf("Processor.Count = %d, want %d", proc.Count, tt.wantCount) + } + }) } } diff --git a/internal/vm/builder/scsi_test.go b/internal/vm/builder/scsi_test.go index a2b9c6a658..aebd584eee 100644 --- a/internal/vm/builder/scsi_test.go +++ b/internal/vm/builder/scsi_test.go @@ -6,11 +6,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestSCSI(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var devices DeviceOptions = b if err := devices.AddSCSIDisk("0", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err == nil { diff --git a/internal/vm/builder/storage_test.go b/internal/vm/builder/storage_test.go index ca558e2d16..88b6ad7b89 100644 --- a/internal/vm/builder/storage_test.go +++ b/internal/vm/builder/storage_test.go @@ -6,11 +6,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestStorageQoS(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var storage StorageQoSOptions = b storage.SetStorageQoS(&hcsschema.StorageQoS{ diff --git a/internal/vm/builder/vpmem_test.go b/internal/vm/builder/vpmem_test.go index cb418d554e..4e681113b2 100644 --- a/internal/vm/builder/vpmem_test.go +++ b/internal/vm/builder/vpmem_test.go @@ -6,11 +6,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestVPMem(t *testing.T) { - b, cs := newBuilder(t, vm.Linux) + b, cs := newBuilder(t) var devices DeviceOptions = b if err := devices.AddVPMemDevice("0", hcsschema.VirtualPMemDevice{HostPath: "pmem.img", ReadOnly: true, ImageFormat: "raw"}); err == nil { t.Fatal("AddVPMemDevice should fail when controller missing") diff --git a/internal/vm/builder/vsmb_test.go b/internal/vm/builder/vsmb_test.go index dd800c47b9..b528153f30 100644 --- a/internal/vm/builder/vsmb_test.go +++ b/internal/vm/builder/vsmb_test.go @@ -7,11 +7,10 @@ import ( "testing" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" ) func TestVSMB(t *testing.T) { - b, cs := newBuilder(t, vm.Windows) + b, cs := newBuilder(t) var devices DeviceOptions = b opts := &hcsschema.VirtualSmbShareOptions{ReadOnly: true} share1 := hcsschema.VirtualSmbShare{ diff --git a/internal/vm/vm.go b/internal/vm/vm.go deleted file mode 100644 index a7a97a9bef..0000000000 --- a/internal/vm/vm.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build windows - -package vm - -// GuestOS signifies the guest operating system that a Utility VM will be running. -type GuestOS string - -const ( - Windows GuestOS = "windows" - Linux GuestOS = "linux" -) diff --git a/internal/vm/vmmanager/lifetime.go b/internal/vm/vmmanager/lifetime.go index 8aadcf411c..cba5cc93f0 100644 --- a/internal/vm/vmmanager/lifetime.go +++ b/internal/vm/vmmanager/lifetime.go @@ -7,7 +7,6 @@ import ( "github.com/Microsoft/go-winio/pkg/guid" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/vm" "github.com/pkg/errors" ) @@ -20,9 +19,6 @@ type LifetimeManager interface { // Only valid after the utility VM has been created. RuntimeID() guid.GUID - // OS will return the operating system type of the Utility VM. This is typically either "windows" or "linux". - OS() vm.GuestOS - // Start will power on the Utility VM and put it into a running state. This will boot the guest OS and start all of the // devices configured on the machine. Start(ctx context.Context) error @@ -68,11 +64,6 @@ func (uvm *UtilityVM) RuntimeID() guid.GUID { return uvm.vmID } -// OS returns the operating system of the utility VM. -func (uvm *UtilityVM) OS() vm.GuestOS { - return uvm.guestOS -} - // Start starts the utility VM. func (uvm *UtilityVM) Start(ctx context.Context) (err error) { if err := uvm.cs.Start(ctx); err != nil { diff --git a/internal/vm/vmmanager/pci.go b/internal/vm/vmmanager/pci.go index 19e7a168f8..bdebbc4e15 100644 --- a/internal/vm/vmmanager/pci.go +++ b/internal/vm/vmmanager/pci.go @@ -9,15 +9,13 @@ import ( "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" - "github.com/Microsoft/hcsshim/osversion" "github.com/pkg/errors" ) // PCIManager manages assiging pci devices to a Utility VM. This is Windows specific at the moment. type PCIManager interface { - // AddDevice adds the pci device identified by `instanceID` to the Utility VM. - // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/instance-ids - AddDevice(ctx context.Context, vmbusGUID string, function hcsschema.VirtualPciFunction) error + // AddDevice adds a pci device identified by `vmbusGUID` to the Utility VM with the provided settings. + AddDevice(ctx context.Context, vmbusGUID string, settings hcsschema.VirtualPciDevice) error // RemoveDevice removes the pci device identified by `vmbusGUID` from the Utility VM. RemoveDevice(ctx context.Context, vmbusGUID string) error @@ -25,21 +23,11 @@ type PCIManager interface { var _ PCIManager = (*UtilityVM)(nil) -func (uvm *UtilityVM) AddDevice(ctx context.Context, vmbusGUID string, function hcsschema.VirtualPciFunction) error { - var propagateAffinity *bool - T := true - if osversion.Get().Build >= osversion.V25H1Server { - propagateAffinity = &T - } +func (uvm *UtilityVM) AddDevice(ctx context.Context, vmbusGUID string, settings hcsschema.VirtualPciDevice) error { request := &hcsschema.ModifySettingRequest{ ResourcePath: fmt.Sprintf(resourcepaths.VirtualPCIResourceFormat, vmbusGUID), RequestType: guestrequest.RequestTypeAdd, - Settings: hcsschema.VirtualPciDevice{ - Functions: []hcsschema.VirtualPciFunction{ - function, - }, - PropagateNumaAffinity: propagateAffinity, - }, + Settings: settings, } if err := uvm.cs.Modify(ctx, request); err != nil { return errors.Wrapf(err, "failed to add PCI device %s", vmbusGUID) diff --git a/internal/vm/vmmanager/uvm.go b/internal/vm/vmmanager/uvm.go index 7298592750..212eadff83 100644 --- a/internal/vm/vmmanager/uvm.go +++ b/internal/vm/vmmanager/uvm.go @@ -8,7 +8,6 @@ import ( "github.com/Microsoft/hcsshim/internal/hcs" "github.com/Microsoft/hcsshim/internal/log" "github.com/Microsoft/hcsshim/internal/logfields" - "github.com/Microsoft/hcsshim/internal/vm" "github.com/Microsoft/hcsshim/internal/vm/builder" "github.com/Microsoft/go-winio/pkg/guid" @@ -21,10 +20,9 @@ import ( // also several optional methods that can be used to determine what the virtual machine // supports and to configure these resources. type UtilityVM struct { - id string - guestOS vm.GuestOS - vmID guid.GUID - cs *hcs.System + id string + vmID guid.GUID + cs *hcs.System } // Create creates a new utility VM with the given ID and builder configuration. @@ -48,8 +46,7 @@ func Create(ctx context.Context, id string, builder *builder.UtilityVM) (*Utilit }() uvm := &UtilityVM{ - id: id, - guestOS: builder.GuestOS(), + id: id, } // Cache the VM ID of the utility VM. From a57f5e4116a953f3aadc4e68d391e8499591a79e Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Sun, 22 Feb 2026 22:37:54 +0530 Subject: [PATCH 5/6] simplify the memory builder interface Signed-off-by: Harsh Rawat --- internal/vm/README.md | 4 ++-- internal/vm/builder/memory.go | 35 ++++----------------------- internal/vm/builder/memory_test.go | 38 ++++++++++++++++-------------- 3 files changed, 27 insertions(+), 50 deletions(-) diff --git a/internal/vm/README.md b/internal/vm/README.md index 2606db0010..ff9adf2419 100644 --- a/internal/vm/README.md +++ b/internal/vm/README.md @@ -39,8 +39,8 @@ configuration, host-side management, and guest-side actions distinct so each lay builder, _ := builder.New("owner") // Configure the VM document. -builder.Memory().SetMemoryLimit(1024) -builder.Processor().SetProcessorConfig(&vm.ProcessorConfig{Count: 2}) +builder.SetMemory(&hcsschema.VirtualMachineMemory{SizeInMB: 1024}) +builder.SetProcessor(&hcsschema.VirtualMachineProcessor{Count: 2}) // ... other builder configuration // Create and start the VM. diff --git a/internal/vm/builder/memory.go b/internal/vm/builder/memory.go index 736fd0647f..b1c1867de3 100644 --- a/internal/vm/builder/memory.go +++ b/internal/vm/builder/memory.go @@ -8,43 +8,18 @@ import ( // MemoryOptions configures memory settings for the Utility VM. type MemoryOptions interface { - // SetMemoryLimit sets the amount of memory in megabytes that the Utility VM will be assigned. - SetMemoryLimit(memoryMB uint64) - // SetMemoryHints sets memory hint settings for the Utility VM. - SetMemoryHints(config *hcsschema.VirtualMachineMemory) - // SetMMIOConfig sets memory mapped IO configurations for the Utility VM. - SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) + // SetMemory sets memory related options for the Utility VM. + SetMemory(config *hcsschema.VirtualMachineMemory) // SetFirmwareFallbackMeasuredSlit sets the SLIT type as "FirmwareFallbackMeasured" for the Utility VM. SetFirmwareFallbackMeasuredSlit() } var _ MemoryOptions = (*UtilityVM)(nil) -func (uvmb *UtilityVM) SetMemoryLimit(memoryMB uint64) { - uvmb.doc.VirtualMachine.ComputeTopology.Memory.SizeInMB = memoryMB -} - -func (uvmb *UtilityVM) SetMemoryHints(config *hcsschema.VirtualMachineMemory) { - if config == nil { - return - } - - memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory - if config.Backing != nil { - memory.Backing = config.Backing - memory.AllowOvercommit = *config.Backing == hcsschema.MemoryBackingType_VIRTUAL +func (uvmb *UtilityVM) SetMemory(config *hcsschema.VirtualMachineMemory) { + if config != nil { + uvmb.doc.VirtualMachine.ComputeTopology.Memory = config } - memory.EnableDeferredCommit = config.EnableDeferredCommit - memory.EnableHotHint = config.EnableHotHint - memory.EnableColdHint = config.EnableColdHint - memory.EnableColdDiscardHint = config.EnableColdDiscardHint -} - -func (uvmb *UtilityVM) SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) { - memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory - memory.LowMMIOGapInMB = lowGapMB - memory.HighMMIOBaseInMB = highBaseMB - memory.HighMMIOGapInMB = highGapMB } func (uvmb *UtilityVM) SetFirmwareFallbackMeasuredSlit() { diff --git a/internal/vm/builder/memory_test.go b/internal/vm/builder/memory_test.go index e48ea8d33d..d1e7949ae3 100644 --- a/internal/vm/builder/memory_test.go +++ b/internal/vm/builder/memory_test.go @@ -13,15 +13,17 @@ func TestMemoryConfig(t *testing.T) { var memory MemoryOptions = b backingVirtual := hcsschema.MemoryBackingType_VIRTUAL - backingPhysical := hcsschema.MemoryBackingType_PHYSICAL - memory.SetMemoryLimit(512) - memory.SetMemoryHints(&hcsschema.VirtualMachineMemory{ + memory.SetMemory(&hcsschema.VirtualMachineMemory{ + SizeInMB: 512, Backing: &backingVirtual, EnableDeferredCommit: true, EnableHotHint: true, EnableColdHint: false, EnableColdDiscardHint: true, + LowMMIOGapInMB: 64, + HighMMIOBaseInMB: 128, + HighMMIOGapInMB: 256, }) mem := cs.VirtualMachine.ComputeTopology.Memory @@ -31,11 +33,16 @@ func TestMemoryConfig(t *testing.T) { if mem.Backing == nil || *mem.Backing != hcsschema.MemoryBackingType_VIRTUAL { t.Fatal("Backing not set to VIRTUAL") } - if !mem.AllowOvercommit || !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { + if !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { t.Fatal("memory hints not applied as expected") } + if mem.LowMMIOGapInMB != 64 || mem.HighMMIOBaseInMB != 128 || mem.HighMMIOGapInMB != 256 { + t.Fatal("MMIO config not applied as expected") + } - memory.SetMemoryHints(&hcsschema.VirtualMachineMemory{ + backingPhysical := hcsschema.MemoryBackingType_PHYSICAL + memory.SetMemory(&hcsschema.VirtualMachineMemory{ + SizeInMB: 1024, Backing: &backingPhysical, EnableDeferredCommit: true, EnableHotHint: true, @@ -44,42 +51,37 @@ func TestMemoryConfig(t *testing.T) { }) mem = cs.VirtualMachine.ComputeTopology.Memory + if mem.SizeInMB != 1024 { + t.Fatalf("SizeInMB = %d, want %d", mem.SizeInMB, 1024) + } if mem.Backing == nil || *mem.Backing != hcsschema.MemoryBackingType_PHYSICAL { t.Fatal("Backing not set to PHYSICAL") } - if mem.AllowOvercommit || !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { + if !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { t.Fatal("memory hints not applied as expected") } - memory.SetMMIOConfig(64, 128, 256) - if mem.LowMMIOGapInMB != 64 || mem.HighMMIOBaseInMB != 128 || mem.HighMMIOGapInMB != 256 { - t.Fatal("MMIO config not applied as expected") - } - memory.SetFirmwareFallbackMeasuredSlit() if mem.SlitType == nil || *mem.SlitType != hcsschema.VirtualSlitType_FIRMWARE_FALLBACK_MEASURED { t.Fatal("SlitType not set to FIRMWARE_FALLBACK_MEASURED") } } -func TestMemoryHintsNilBacking(t *testing.T) { +func TestMemoryNilConfig(t *testing.T) { b, cs := newBuilder(t) var memory MemoryOptions = b defer func() { if r := recover(); r != nil { - t.Fatalf("SetMemoryHints panicked: %v", r) + t.Fatalf("SetMemory panicked: %v", r) } }() - memory.SetMemoryHints(nil) - memory.SetMemoryHints(&hcsschema.VirtualMachineMemory{}) + memory.SetMemory(nil) + memory.SetMemory(&hcsschema.VirtualMachineMemory{}) mem := cs.VirtualMachine.ComputeTopology.Memory if mem.Backing != nil { t.Fatal("Backing should remain nil when not provided") } - if mem.AllowOvercommit { - t.Fatal("AllowOvercommit should remain false when Backing is nil") - } } From 0172f1035477995b6370f2c86c5502e7cec4549c Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Mon, 23 Feb 2026 01:40:28 +0530 Subject: [PATCH 6/6] removed the builder package as it was an unnecessary abstraction Signed-off-by: Harsh Rawat --- internal/vm/README.md | 38 +++--- internal/vm/builder/boot.go | 34 ------ internal/vm/builder/boot_test.go | 63 ---------- internal/vm/builder/builder.go | 62 ---------- internal/vm/builder/builder_test.go | 32 ----- internal/vm/builder/device.go | 112 ----------------- internal/vm/builder/device_test.go | 125 ------------------- internal/vm/builder/memory.go | 28 ----- internal/vm/builder/memory_test.go | 87 ------------- internal/vm/builder/numa.go | 25 ---- internal/vm/builder/numa_test.go | 96 --------------- internal/vm/builder/processor.go | 27 ---- internal/vm/builder/processor_test.go | 170 -------------------------- internal/vm/builder/scsi.go | 32 ----- internal/vm/builder/scsi_test.go | 47 ------- internal/vm/builder/storage.go | 28 ----- internal/vm/builder/storage_test.go | 25 ---- internal/vm/builder/vpmem.go | 24 ---- internal/vm/builder/vpmem_test.go | 31 ----- internal/vm/builder/vsmb.go | 25 ---- internal/vm/builder/vsmb_test.go | 48 -------- internal/vm/vmmanager/uvm.go | 14 +-- 22 files changed, 20 insertions(+), 1153 deletions(-) delete mode 100644 internal/vm/builder/boot.go delete mode 100644 internal/vm/builder/boot_test.go delete mode 100644 internal/vm/builder/builder.go delete mode 100644 internal/vm/builder/builder_test.go delete mode 100644 internal/vm/builder/device.go delete mode 100644 internal/vm/builder/device_test.go delete mode 100644 internal/vm/builder/memory.go delete mode 100644 internal/vm/builder/memory_test.go delete mode 100644 internal/vm/builder/numa.go delete mode 100644 internal/vm/builder/numa_test.go delete mode 100644 internal/vm/builder/processor.go delete mode 100644 internal/vm/builder/processor_test.go delete mode 100644 internal/vm/builder/scsi.go delete mode 100644 internal/vm/builder/scsi_test.go delete mode 100644 internal/vm/builder/storage.go delete mode 100644 internal/vm/builder/storage_test.go delete mode 100644 internal/vm/builder/vpmem.go delete mode 100644 internal/vm/builder/vpmem_test.go delete mode 100644 internal/vm/builder/vsmb.go delete mode 100644 internal/vm/builder/vsmb_test.go diff --git a/internal/vm/README.md b/internal/vm/README.md index ff9adf2419..6f9fccf49b 100644 --- a/internal/vm/README.md +++ b/internal/vm/README.md @@ -1,34 +1,28 @@ # VM Package -This directory defines the utility VM (UVM) contracts and separates responsibilities into three layers. The goal is to keep -configuration, host-side management, and guest-side actions distinct so each layer can evolve independently. +This directory defines the utility VM (UVM) contracts and separates responsibilities into two layers. The goal is to keep +host-side management and guest-side actions distinct so each layer can evolve independently. -1. **Builder**: constructs an HCS compute system configuration used to create a VM. -2. **VM Manager**: manages host-side VM configuration and lifecycle (NICs, SCSI, VPMem, etc.). -3. **Guest Manager**: intended for guest-side actions (for example, mounting a disk). +1. **VM Manager**: manages host-side VM configuration and lifecycle (NICs, SCSI, VPMem, etc.). +2. **Guest Manager**: intended for guest-side actions (for example, mounting a disk). **Note that** this layer does not store UVM host or guest side state. That will be part of the orchestration layer above it. ## Packages and Responsibilities -- `internal/vm/builder` - - Interface definitions for shaping the VM configuration (`Builder` interface). - - Concrete implementation of `Builder` for building `hcsschema.ComputeSystem` documents. - - Provides a fluent API for configuring all aspects of the VM document. - - Presently, this package is tightly coupled with HCS backend. - `internal/vm/vmmanager` - - Interface definitions for UVM lifecycle and host-side management. - - Concrete implementation of `UVM` for running and managing a UVM instance. + - Concrete `UtilityVM` struct for running and managing a UVM instance. + - Defines granular manager interfaces scoped to individual resource concerns (`LifetimeManager`, `NetworkManager`, `SCSIManager`, `PCIManager`, `PipeManager`, `Plan9Manager`, `VMSocketManager`, `VPMemManager`, `VSMBManager`, `ResourceManager`), all implemented by `UtilityVM`. - Presently, this package is tightly coupled with HCS backend and only runs HCS backed UVMs. - Owns lifecycle calls (start/terminate/close) and host-side modifications (NICs, SCSI, VPMem, pipes, VSMB, Plan9). - - Allows creation of the UVM using `vmmanager.Create` which takes a `Builder` and produces a running UVM. + - Allows creation of the UVM using `vmmanager.Create` which takes an `hcsschema.ComputeSystem` config and produces a running UVM. - `internal/vm/guestmanager` - Reserved for guest-level actions such as mounting disks or performing in-guest configuration. - Currently empty and intended to grow as guest actions are formalized. ## Typical Flow -1. Build the config using the builder interfaces. +1. Build the `hcsschema.ComputeSystem` configuration. 2. Create the VM using the VM manager. 3. Use manager interfaces for lifecycle and host-side changes. 4. Use guest manager interfaces for in-guest actions (when available). @@ -36,24 +30,20 @@ configuration, host-side management, and guest-side actions distinct so each lay ## Example (High Level) ``` -builder, _ := builder.New("owner") - -// Configure the VM document. -builder.SetMemory(&hcsschema.VirtualMachineMemory{SizeInMB: 1024}) -builder.SetProcessor(&hcsschema.VirtualMachineProcessor{Count: 2}) -// ... other builder configuration +config := &hcsschema.ComputeSystem{ + // Configure the VM document. +} // Create and start the VM. -uvm, _ := vmmanager.Create(ctx, "uvm-id", builder) -_ = uvm.LifetimeManager().Start(ctx) +uvm, _ := vmmanager.Create(ctx, "uvm-id", config) +_ = uvm.Start(ctx) // Apply host-side updates. -_ = uvm.NetworkManager().AddNIC(ctx, nicID, endpointID, macAddr) +_ = uvm.AddNIC(ctx, nicID, settings) ``` ## Layer Boundaries (Quick Reference) -- **Builder**: static, pre-create configuration only. No host mutations. - **VM Manager**: host-side changes and lifecycle operations on an existing UVM. - **Guest Manager**: guest-side actions, scoped to work that requires in-guest context. diff --git a/internal/vm/builder/boot.go b/internal/vm/builder/boot.go deleted file mode 100644 index 4f5abcc057..0000000000 --- a/internal/vm/builder/boot.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/osversion" - - "github.com/pkg/errors" -) - -// BootOptions configures boot settings for the Utility VM. -type BootOptions interface { - // SetUEFIBoot sets UEFI configurations for booting a Utility VM. - SetUEFIBoot(bootEntry *hcsschema.UefiBootEntry) - // SetLinuxKernelDirectBoot sets Linux direct boot configurations for booting a Utility VM. - SetLinuxKernelDirectBoot(options *hcsschema.LinuxKernelDirect) error -} - -var _ BootOptions = (*UtilityVM)(nil) - -func (uvmb *UtilityVM) SetUEFIBoot(bootEntry *hcsschema.UefiBootEntry) { - uvmb.doc.VirtualMachine.Chipset.Uefi = &hcsschema.Uefi{ - BootThis: bootEntry, - } -} - -func (uvmb *UtilityVM) SetLinuxKernelDirectBoot(options *hcsschema.LinuxKernelDirect) error { - if osversion.Get().Build < 18286 { - return errors.New("Linux kernel direct boot requires at least Windows version 18286") - } - uvmb.doc.VirtualMachine.Chipset.LinuxKernelDirect = options - return nil -} diff --git a/internal/vm/builder/boot_test.go b/internal/vm/builder/boot_test.go deleted file mode 100644 index bfac2d5cfc..0000000000 --- a/internal/vm/builder/boot_test.go +++ /dev/null @@ -1,63 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestBootConfig(t *testing.T) { - b, cs := newBuilder(t) - var mboot BootOptions = b - - bootEntry := &hcsschema.UefiBootEntry{ - DevicePath: "path", - DeviceType: "VmbFs", - VmbFsRootPath: "root", - OptionalData: "args", - } - mboot.SetUEFIBoot(bootEntry) - - if cs.VirtualMachine.Chipset.Uefi == nil || cs.VirtualMachine.Chipset.Uefi.BootThis == nil { - t.Fatal("UEFI boot not applied") - } - - got := cs.VirtualMachine.Chipset.Uefi.BootThis - if got.DevicePath != "path" { - t.Fatalf("UEFI DevicePath = %q, want %q", got.DevicePath, "path") - } - if got.DeviceType != "VmbFs" { - t.Fatalf("UEFI DeviceType = %q, want %q", got.DeviceType, "VmbFs") - } - if got.VmbFsRootPath != "root" { - t.Fatalf("UEFI VmbFsRootPath = %q, want %q", got.VmbFsRootPath, "root") - } - if got.OptionalData != "args" { - t.Fatalf("UEFI OptionalData = %q, want %q", got.OptionalData, "args") - } - - linuxBoot := &hcsschema.LinuxKernelDirect{ - KernelFilePath: "kernel", - InitRdPath: "initrd", - KernelCmdLine: "cmd", - } - err := mboot.SetLinuxKernelDirectBoot(linuxBoot) - if err != nil { - t.Fatalf("SetLinuxKernelDirectBoot error = %v", err) - } - if cs.VirtualMachine.Chipset.LinuxKernelDirect == nil { - t.Fatal("LinuxKernelDirect not applied") - } - lkd := cs.VirtualMachine.Chipset.LinuxKernelDirect - if lkd.KernelFilePath != "kernel" { - t.Fatalf("LinuxKernelDirect KernelFilePath = %q, want %q", lkd.KernelFilePath, "kernel") - } - if lkd.InitRdPath != "initrd" { - t.Fatalf("LinuxKernelDirect InitRdPath = %q, want %q", lkd.InitRdPath, "initrd") - } - if lkd.KernelCmdLine != "cmd" { - t.Fatalf("LinuxKernelDirect KernelCmdLine = %q, want %q", lkd.KernelCmdLine, "cmd") - } -} diff --git a/internal/vm/builder/builder.go b/internal/vm/builder/builder.go deleted file mode 100644 index 79a383920f..0000000000 --- a/internal/vm/builder/builder.go +++ /dev/null @@ -1,62 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/schemaversion" - - "github.com/pkg/errors" -) - -var ( - errAlreadySet = errors.New("field has already been set") -) - -// UtilityVM is used to build a schema document for creating a Utility VM. -// It provides methods for configuring various aspects of the Utility VM -// such as memory, processors, devices, boot options, and storage QoS settings. -type UtilityVM struct { - doc *hcsschema.ComputeSystem - assignedDevices map[hcsschema.VirtualPciFunction]*vPCIDevice -} - -// New returns the concrete builder, and callers are expected to use the -// interface views (for example, NumaOptions, MemoryOptions) as needed. -// This follows the "accept interfaces, return structs" convention. -func New(owner string) (*UtilityVM, error) { - doc := &hcsschema.ComputeSystem{ - Owner: owner, - SchemaVersion: schemaversion.SchemaV21(), - // Terminate the UVM when the last handle is closed. - // When we need to support impactless updates this will need to be configurable. - ShouldTerminateOnLastHandleClosed: true, - VirtualMachine: &hcsschema.VirtualMachine{ - StopOnReset: true, - Chipset: &hcsschema.Chipset{}, - ComputeTopology: &hcsschema.Topology{ - Memory: &hcsschema.VirtualMachineMemory{}, - Processor: &hcsschema.VirtualMachineProcessor{}, - }, - Devices: &hcsschema.Devices{ - HvSocket: &hcsschema.HvSocket2{ - HvSocketConfig: &hcsschema.HvSocketSystemConfig{ - // Allow administrators and SYSTEM to bind to vsock sockets - // so that we can create a GCS log socket. - DefaultBindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", - ServiceTable: make(map[string]hcsschema.HvSocketServiceConfig), - }, - }, - }, - }, - } - - return &UtilityVM{ - doc: doc, - assignedDevices: make(map[hcsschema.VirtualPciFunction]*vPCIDevice), - }, nil -} - -func (uvmb *UtilityVM) Get() *hcsschema.ComputeSystem { - return uvmb.doc -} diff --git a/internal/vm/builder/builder_test.go b/internal/vm/builder/builder_test.go deleted file mode 100644 index 0509c5ccd3..0000000000 --- a/internal/vm/builder/builder_test.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func newBuilder(t *testing.T) (*UtilityVM, *hcsschema.ComputeSystem) { - t.Helper() - b, err := New("owner") - if err != nil { - t.Fatalf("New() error = %v", err) - } - - return b, b.Get() -} - -func TestNewBuilder_DefaultFields(t *testing.T) { - _, cs := newBuilder(t) - if cs.VirtualMachine == nil { - t.Fatal("VirtualMachine should be initialized") - } - if cs.VirtualMachine.Devices == nil { - t.Fatal("Devices should be initialized") - } - if cs.VirtualMachine.Devices.HvSocket == nil { - t.Fatal("HvSocket should be initialized") - } -} diff --git a/internal/vm/builder/device.go b/internal/vm/builder/device.go deleted file mode 100644 index 5e131e05da..0000000000 --- a/internal/vm/builder/device.go +++ /dev/null @@ -1,112 +0,0 @@ -//go:build windows - -package builder - -import ( - "strconv" - "strings" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - - "github.com/Microsoft/go-winio/pkg/guid" - "github.com/pkg/errors" -) - -const ( - pipePrefix = `\\.\pipe\` -) - -// vPCIDevice represents a vpci device. -type vPCIDevice struct { - // vmbusGUID is the instance ID for this device when it is exposed via VMBus - vmbusGUID string - // deviceInstanceID is the instance ID of the device on the host - deviceInstanceID string - // virtualFunctionIndex is the function index for the pci device to assign - virtualFunctionIndex uint16 - // refCount stores the number of references to this device in the UVM - refCount uint32 -} - -// DeviceOptions configures device settings for the Utility VM. -type DeviceOptions interface { - // AddVPCIDevice adds a PCI device to the Utility VM. - // If the device is already added, we return an error. - AddVPCIDevice(vmbusGUID guid.GUID, device hcsschema.VirtualPciFunction, numaAffinity bool) error - // AddSCSIController adds a SCSI controller to the Utility VM with the specified ID. - AddSCSIController(id string) - // AddSCSIDisk adds a SCSI disk to the Utility VM under the specified controller and LUN. - AddSCSIDisk(controller string, lun string, disk hcsschema.Attachment) error - // AddVPMemController adds a VPMem controller to the Utility VM with the specified maximum devices and size. - AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) - // AddVPMemDevice adds a VPMem device to the Utility VM under the VPMem controller. - AddVPMemDevice(id string, device hcsschema.VirtualPMemDevice) error - // AddVSMB initializes the VSMB settings for the Utility VM. - AddVSMB(settings hcsschema.VirtualSmb) - // AddVSMBShare adds a VSMB share to the Utility VM. - AddVSMBShare(share hcsschema.VirtualSmbShare) error - // AddPlan9 adds a Plan9 device to the Utility VM with the specified settings. - AddPlan9(settings *hcsschema.Plan9) - // SetSerialConsole sets up a serial console for `port`. Output will be relayed to the listener specified - // by `listenerPath`. For HCS `listenerPath` this is expected to be a path to a named pipe. - SetSerialConsole(port uint32, listenerPath string) error - // EnableGraphicsConsole enables a graphics console for the Utility VM. - EnableGraphicsConsole() -} - -var _ DeviceOptions = (*UtilityVM)(nil) - -func (uvmb *UtilityVM) AddVPCIDevice(vmbusGUID guid.GUID, device hcsschema.VirtualPciFunction, numaAffinity bool) error { - _, ok := uvmb.assignedDevices[device] - if ok { - return errors.Wrapf(errAlreadySet, "device %v already assigned to utility VM", device) - } - - var propagateAffinity *bool - if numaAffinity { - propagateAffinity = &numaAffinity - } - - if uvmb.doc.VirtualMachine.Devices.VirtualPci == nil { - uvmb.doc.VirtualMachine.Devices.VirtualPci = make(map[string]hcsschema.VirtualPciDevice) - } - - uvmb.doc.VirtualMachine.Devices.VirtualPci[vmbusGUID.String()] = hcsschema.VirtualPciDevice{ - Functions: []hcsschema.VirtualPciFunction{ - device, - }, - PropagateNumaAffinity: propagateAffinity, - } - - uvmb.assignedDevices[device] = &vPCIDevice{ - vmbusGUID: vmbusGUID.String(), - deviceInstanceID: device.DeviceInstancePath, - virtualFunctionIndex: device.VirtualFunction, - refCount: 1, - } - - return nil -} - -func (uvmb *UtilityVM) SetSerialConsole(port uint32, listenerPath string) error { - if !strings.HasPrefix(listenerPath, pipePrefix) { - return errors.New("listener for serial console is not a named pipe") - } - - uvmb.doc.VirtualMachine.Devices.ComPorts = map[string]hcsschema.ComPort{ - strconv.Itoa(int(port)): { // "0" would be COM1 - NamedPipe: listenerPath, - }, - } - return nil -} - -func (uvmb *UtilityVM) EnableGraphicsConsole() { - uvmb.doc.VirtualMachine.Devices.Keyboard = &hcsschema.Keyboard{} - uvmb.doc.VirtualMachine.Devices.EnhancedModeVideo = &hcsschema.EnhancedModeVideo{} - uvmb.doc.VirtualMachine.Devices.VideoMonitor = &hcsschema.VideoMonitor{} -} - -func (uvmb *UtilityVM) AddPlan9(settings *hcsschema.Plan9) { - uvmb.doc.VirtualMachine.Devices.Plan9 = settings -} diff --git a/internal/vm/builder/device_test.go b/internal/vm/builder/device_test.go deleted file mode 100644 index e135131687..0000000000 --- a/internal/vm/builder/device_test.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build windows - -package builder - -import ( - "strconv" - "testing" - - "github.com/Microsoft/go-winio/pkg/guid" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/pkg/errors" -) - -// administratorsPipePrefix is the protected pipe prefix for administrators. -// It is also covered by pipePrefix since it starts with `\\.\pipe\`. -const administratorsPipePrefix = `\\.\pipe\ProtectedPrefix\Administrators\` - -func TestVPCIDevice(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - device := hcsschema.VirtualPciFunction{DeviceInstancePath: "PCI\\VEN_1234", VirtualFunction: 2} - - vmbusGUID, err := guid.NewV4() - if err != nil { - t.Fatalf("guid.NewV4 error = %v", err) - } - - if err := devices.AddVPCIDevice(vmbusGUID, device, true); err != nil { - t.Fatalf("AddVPCIDevice error = %v", err) - } - if len(cs.VirtualMachine.Devices.VirtualPci) != 1 { - t.Fatalf("VirtualPci entries = %d, want 1", len(cs.VirtualMachine.Devices.VirtualPci)) - } - entry, ok := cs.VirtualMachine.Devices.VirtualPci[vmbusGUID.String()] - if !ok { - t.Fatal("VirtualPci entry not found for provided vmbusGUID") - } - if len(entry.Functions) != 1 { - t.Fatalf("VirtualPci Functions = %d, want 1", len(entry.Functions)) - } - if entry.Functions[0].DeviceInstancePath != device.DeviceInstancePath || entry.Functions[0].VirtualFunction != device.VirtualFunction { - t.Fatal("VPCI function not applied as expected") - } - if entry.PropagateNumaAffinity == nil || !*entry.PropagateNumaAffinity { - t.Fatal("PropagateNumaAffinity should be true") - } - - dupGUID, err := guid.NewV4() - if err != nil { - t.Fatalf("guid.NewV4 error = %v", err) - } - - if err := devices.AddVPCIDevice(dupGUID, device, false); !errors.Is(err, errAlreadySet) { - t.Fatalf("AddVPCIDevice duplicate error = %v, want %v", err, errAlreadySet) - } -} - -func TestSerialConsoleAndGraphics(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - if err := devices.SetSerialConsole(1, "not-a-pipe"); err == nil { - t.Fatal("SetSerialConsole should reject non-pipe path") - } - - pipePath := `\\.\pipe\serial` - if err := devices.SetSerialConsole(1, pipePath); err != nil { - t.Fatalf("SetSerialConsole error = %v", err) - } - key := strconv.Itoa(1) - if cs.VirtualMachine.Devices.ComPorts[key].NamedPipe != pipePath { - t.Fatal("serial console named pipe not set as expected") - } - - adminPipePath := administratorsPipePrefix + "serial" - if err := devices.SetSerialConsole(1, adminPipePath); err != nil { - t.Fatalf("SetSerialConsole should accept administrators pipe prefix, error = %v", err) - } - if cs.VirtualMachine.Devices.ComPorts[key].NamedPipe != adminPipePath { - t.Fatal("serial console administrators named pipe not set as expected") - } - - devices.EnableGraphicsConsole() - if cs.VirtualMachine.Devices.Keyboard == nil || cs.VirtualMachine.Devices.EnhancedModeVideo == nil || cs.VirtualMachine.Devices.VideoMonitor == nil { - t.Fatal("graphics console devices not enabled") - } -} - -func TestAddPlan9(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - - share := hcsschema.Plan9Share{ - Name: "data", - AccessName: "data", - Path: "/host/path", - Port: 564, - ReadOnly: true, - } - settings := &hcsschema.Plan9{Shares: []hcsschema.Plan9Share{share}} - devices.AddPlan9(settings) - - if cs.VirtualMachine.Devices.Plan9 == nil { - t.Fatal("Plan9 should be set") - } - if len(cs.VirtualMachine.Devices.Plan9.Shares) != 1 { - t.Fatalf("Plan9 Shares = %d, want 1", len(cs.VirtualMachine.Devices.Plan9.Shares)) - } - got := cs.VirtualMachine.Devices.Plan9.Shares[0] - if got.Name != share.Name || got.AccessName != share.AccessName || got.Path != share.Path || got.Port != share.Port || got.ReadOnly != share.ReadOnly { - t.Fatalf("Plan9 share not applied as expected: got %+v, want %+v", got, share) - } -} - -func TestAddPlan9_Nil(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - - // First set a Plan9 config, then overwrite with nil. - devices.AddPlan9(&hcsschema.Plan9{Shares: []hcsschema.Plan9Share{{Name: "tmp"}}}) - devices.AddPlan9(nil) - - if cs.VirtualMachine.Devices.Plan9 != nil { - t.Fatal("Plan9 should be nil after AddPlan9(nil)") - } -} diff --git a/internal/vm/builder/memory.go b/internal/vm/builder/memory.go deleted file mode 100644 index b1c1867de3..0000000000 --- a/internal/vm/builder/memory.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -// MemoryOptions configures memory settings for the Utility VM. -type MemoryOptions interface { - // SetMemory sets memory related options for the Utility VM. - SetMemory(config *hcsschema.VirtualMachineMemory) - // SetFirmwareFallbackMeasuredSlit sets the SLIT type as "FirmwareFallbackMeasured" for the Utility VM. - SetFirmwareFallbackMeasuredSlit() -} - -var _ MemoryOptions = (*UtilityVM)(nil) - -func (uvmb *UtilityVM) SetMemory(config *hcsschema.VirtualMachineMemory) { - if config != nil { - uvmb.doc.VirtualMachine.ComputeTopology.Memory = config - } -} - -func (uvmb *UtilityVM) SetFirmwareFallbackMeasuredSlit() { - firmwareFallbackMeasured := hcsschema.VirtualSlitType_FIRMWARE_FALLBACK_MEASURED - uvmb.doc.VirtualMachine.ComputeTopology.Memory.SlitType = &firmwareFallbackMeasured -} diff --git a/internal/vm/builder/memory_test.go b/internal/vm/builder/memory_test.go deleted file mode 100644 index d1e7949ae3..0000000000 --- a/internal/vm/builder/memory_test.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestMemoryConfig(t *testing.T) { - b, cs := newBuilder(t) - var memory MemoryOptions = b - - backingVirtual := hcsschema.MemoryBackingType_VIRTUAL - - memory.SetMemory(&hcsschema.VirtualMachineMemory{ - SizeInMB: 512, - Backing: &backingVirtual, - EnableDeferredCommit: true, - EnableHotHint: true, - EnableColdHint: false, - EnableColdDiscardHint: true, - LowMMIOGapInMB: 64, - HighMMIOBaseInMB: 128, - HighMMIOGapInMB: 256, - }) - - mem := cs.VirtualMachine.ComputeTopology.Memory - if mem.SizeInMB != 512 { - t.Fatalf("SizeInMB = %d, want %d", mem.SizeInMB, 512) - } - if mem.Backing == nil || *mem.Backing != hcsschema.MemoryBackingType_VIRTUAL { - t.Fatal("Backing not set to VIRTUAL") - } - if !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { - t.Fatal("memory hints not applied as expected") - } - if mem.LowMMIOGapInMB != 64 || mem.HighMMIOBaseInMB != 128 || mem.HighMMIOGapInMB != 256 { - t.Fatal("MMIO config not applied as expected") - } - - backingPhysical := hcsschema.MemoryBackingType_PHYSICAL - memory.SetMemory(&hcsschema.VirtualMachineMemory{ - SizeInMB: 1024, - Backing: &backingPhysical, - EnableDeferredCommit: true, - EnableHotHint: true, - EnableColdHint: false, - EnableColdDiscardHint: true, - }) - - mem = cs.VirtualMachine.ComputeTopology.Memory - if mem.SizeInMB != 1024 { - t.Fatalf("SizeInMB = %d, want %d", mem.SizeInMB, 1024) - } - if mem.Backing == nil || *mem.Backing != hcsschema.MemoryBackingType_PHYSICAL { - t.Fatal("Backing not set to PHYSICAL") - } - if !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { - t.Fatal("memory hints not applied as expected") - } - - memory.SetFirmwareFallbackMeasuredSlit() - if mem.SlitType == nil || *mem.SlitType != hcsschema.VirtualSlitType_FIRMWARE_FALLBACK_MEASURED { - t.Fatal("SlitType not set to FIRMWARE_FALLBACK_MEASURED") - } -} - -func TestMemoryNilConfig(t *testing.T) { - b, cs := newBuilder(t) - var memory MemoryOptions = b - - defer func() { - if r := recover(); r != nil { - t.Fatalf("SetMemory panicked: %v", r) - } - }() - - memory.SetMemory(nil) - memory.SetMemory(&hcsschema.VirtualMachineMemory{}) - - mem := cs.VirtualMachine.ComputeTopology.Memory - if mem.Backing != nil { - t.Fatal("Backing should remain nil when not provided") - } -} diff --git a/internal/vm/builder/numa.go b/internal/vm/builder/numa.go deleted file mode 100644 index 3051fad0ec..0000000000 --- a/internal/vm/builder/numa.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -// NumaOptions configures NUMA settings for the Utility VM. -type NumaOptions interface { - // SetNUMAProcessorsSettings sets the NUMA processor settings for the Utility VM. - SetNUMAProcessorsSettings(numaProcessors *hcsschema.NumaProcessors) - // SetNUMASettings sets the NUMA settings for the Utility VM. - SetNUMASettings(numa *hcsschema.Numa) -} - -var _ NumaOptions = (*UtilityVM)(nil) - -func (uvmb *UtilityVM) SetNUMAProcessorsSettings(numaProcessors *hcsschema.NumaProcessors) { - uvmb.doc.VirtualMachine.ComputeTopology.Processor.NumaProcessorsSettings = numaProcessors -} - -func (uvmb *UtilityVM) SetNUMASettings(numa *hcsschema.Numa) { - uvmb.doc.VirtualMachine.ComputeTopology.Numa = numa -} diff --git a/internal/vm/builder/numa_test.go b/internal/vm/builder/numa_test.go deleted file mode 100644 index f96993d0ae..0000000000 --- a/internal/vm/builder/numa_test.go +++ /dev/null @@ -1,96 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestNUMASettings(t *testing.T) { - b, cs := newBuilder(t) - var numaManager NumaOptions = b - virtualNodeCount := uint8(2) - maxSizePerNode := uint64(4096) - numaProcessors := &hcsschema.NumaProcessors{ - CountPerNode: hcsschema.Range{Max: 4}, - NodePerSocket: 1, - } - numa := &hcsschema.Numa{ - VirtualNodeCount: virtualNodeCount, - PreferredPhysicalNodes: []int64{0, 1}, - Settings: []hcsschema.NumaSetting{ - { - VirtualNodeNumber: 0, - PhysicalNodeNumber: 0, - VirtualSocketNumber: 0, - CountOfProcessors: 2, - CountOfMemoryBlocks: 1024, - MemoryBackingType: hcsschema.MemoryBackingType_VIRTUAL, - }, - { - VirtualNodeNumber: 1, - PhysicalNodeNumber: 1, - VirtualSocketNumber: 1, - CountOfProcessors: 2, - CountOfMemoryBlocks: 1024, - MemoryBackingType: hcsschema.MemoryBackingType_PHYSICAL, - }, - }, - MaxSizePerNode: maxSizePerNode, - } - - numaManager.SetNUMAProcessorsSettings(numaProcessors) - numaManager.SetNUMASettings(numa) - - gotProcessors := cs.VirtualMachine.ComputeTopology.Processor.NumaProcessorsSettings - if gotProcessors == nil { - t.Fatal("NUMA processor settings not applied") - } - if gotProcessors.CountPerNode.Max != 4 { - t.Fatalf("CountPerNode.Max = %d, want %d", gotProcessors.CountPerNode.Max, 4) - } - if gotProcessors.NodePerSocket != 1 { - t.Fatalf("NodePerSocket = %d, want %d", gotProcessors.NodePerSocket, 1) - } - - gotNUMA := cs.VirtualMachine.ComputeTopology.Numa - if gotNUMA == nil { - t.Fatal("NUMA settings not applied") - } - if gotNUMA.VirtualNodeCount != virtualNodeCount { - t.Fatalf("VirtualNodeCount = %d, want %d", gotNUMA.VirtualNodeCount, virtualNodeCount) - } - if gotNUMA.MaxSizePerNode != maxSizePerNode { - t.Fatalf("MaxSizePerNode = %d, want %d", gotNUMA.MaxSizePerNode, maxSizePerNode) - } - if len(gotNUMA.PreferredPhysicalNodes) != 2 || gotNUMA.PreferredPhysicalNodes[0] != 0 || gotNUMA.PreferredPhysicalNodes[1] != 1 { - t.Fatalf("PreferredPhysicalNodes = %v, want [0 1]", gotNUMA.PreferredPhysicalNodes) - } - if len(gotNUMA.Settings) != 2 { - t.Fatalf("Settings length = %d, want %d", len(gotNUMA.Settings), 2) - } - - first := gotNUMA.Settings[0] - if first.VirtualNodeNumber != 0 || first.PhysicalNodeNumber != 0 || first.VirtualSocketNumber != 0 { - t.Fatal("first NUMA setting node/socket numbers not applied as expected") - } - if first.CountOfProcessors != 2 || first.CountOfMemoryBlocks != 1024 { - t.Fatal("first NUMA setting processor/memory counts not applied as expected") - } - if first.MemoryBackingType != hcsschema.MemoryBackingType_VIRTUAL { - t.Fatalf("first NUMA setting MemoryBackingType = %s, want %s", first.MemoryBackingType, hcsschema.MemoryBackingType_VIRTUAL) - } - - second := gotNUMA.Settings[1] - if second.VirtualNodeNumber != 1 || second.PhysicalNodeNumber != 1 || second.VirtualSocketNumber != 1 { - t.Fatal("second NUMA setting node/socket numbers not applied as expected") - } - if second.CountOfProcessors != 2 || second.CountOfMemoryBlocks != 1024 { - t.Fatal("second NUMA setting processor/memory counts not applied as expected") - } - if second.MemoryBackingType != hcsschema.MemoryBackingType_PHYSICAL { - t.Fatalf("second NUMA setting MemoryBackingType = %s, want %s", second.MemoryBackingType, hcsschema.MemoryBackingType_PHYSICAL) - } -} diff --git a/internal/vm/builder/processor.go b/internal/vm/builder/processor.go deleted file mode 100644 index fac0d460e1..0000000000 --- a/internal/vm/builder/processor.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -// ProcessorOptions configures processor settings for the Utility VM. -type ProcessorOptions interface { - // SetProcessor sets processor related options for the Utility VM - SetProcessor(config *hcsschema.VirtualMachineProcessor) - // SetCPUGroup sets the CPU group that the Utility VM will belong to on a Windows host. - SetCPUGroup(cpuGroup *hcsschema.CpuGroup) -} - -var _ ProcessorOptions = (*UtilityVM)(nil) - -func (uvmb *UtilityVM) SetProcessor(config *hcsschema.VirtualMachineProcessor) { - if config != nil { - uvmb.doc.VirtualMachine.ComputeTopology.Processor = config - } -} - -func (uvmb *UtilityVM) SetCPUGroup(cpuGroup *hcsschema.CpuGroup) { - uvmb.doc.VirtualMachine.ComputeTopology.Processor.CpuGroup = cpuGroup -} diff --git a/internal/vm/builder/processor_test.go b/internal/vm/builder/processor_test.go deleted file mode 100644 index 34e5a5c938..0000000000 --- a/internal/vm/builder/processor_test.go +++ /dev/null @@ -1,170 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestSetProcessor(t *testing.T) { - tests := []struct { - name string - // configs is a sequence of SetProcessor calls to apply. - configs []*hcsschema.VirtualMachineProcessor - // wantNoop when true means the final Processor should be the default (non-nil, zero-value). - wantNoop bool - // want is the expected final Processor state (ignored when wantNoop is true). - want *hcsschema.VirtualMachineProcessor - }{ - { - name: "all fields", - configs: []*hcsschema.VirtualMachineProcessor{ - {Count: 4, Limit: 2500, Weight: 200, Reservation: 1000}, - }, - want: &hcsschema.VirtualMachineProcessor{Count: 4, Limit: 2500, Weight: 200, Reservation: 1000}, - }, - { - name: "nil is no-op", - configs: []*hcsschema.VirtualMachineProcessor{nil}, - wantNoop: true, - }, - { - name: "overwrite replaces completely", - configs: []*hcsschema.VirtualMachineProcessor{ - {Count: 2, Weight: 100}, - {Count: 8, Limit: 5000}, - }, - want: &hcsschema.VirtualMachineProcessor{Count: 8, Limit: 5000}, - }, - { - name: "nil after set is no-op", - configs: []*hcsschema.VirtualMachineProcessor{ - {Count: 4, Weight: 300}, - nil, - }, - want: &hcsschema.VirtualMachineProcessor{Count: 4, Weight: 300}, - }, - { - name: "count only", - configs: []*hcsschema.VirtualMachineProcessor{ - {Count: 16}, - }, - want: &hcsschema.VirtualMachineProcessor{Count: 16}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b, cs := newBuilder(t) - var processor ProcessorOptions = b - - for _, cfg := range tt.configs { - processor.SetProcessor(cfg) - } - - proc := cs.VirtualMachine.ComputeTopology.Processor - if proc == nil { - t.Fatal("Processor should never be nil") - } - - if tt.wantNoop { - // Default processor from New() is a zero-value struct. - if proc.Count != 0 || proc.Limit != 0 || proc.Weight != 0 || proc.Reservation != 0 { - t.Fatalf("expected default processor, got %+v", proc) - } - return - } - - if proc.Count != tt.want.Count { - t.Fatalf("Processor.Count = %d, want %d", proc.Count, tt.want.Count) - } - if proc.Limit != tt.want.Limit { - t.Fatalf("Processor.Limit = %d, want %d", proc.Limit, tt.want.Limit) - } - if proc.Weight != tt.want.Weight { - t.Fatalf("Processor.Weight = %d, want %d", proc.Weight, tt.want.Weight) - } - if proc.Reservation != tt.want.Reservation { - t.Fatalf("Processor.Reservation = %d, want %d", proc.Reservation, tt.want.Reservation) - } - }) - } -} - -func TestSetCPUGroup(t *testing.T) { - tests := []struct { - name string - // processor is an optional SetProcessor call before SetCPUGroup. - // nil means use the default processor from New(). - processor *hcsschema.VirtualMachineProcessor - // groups is a sequence of SetCPUGroup calls to apply. - groups []*hcsschema.CpuGroup - // wantGroupID is the expected CpuGroup.Id after all calls. Empty means CpuGroup should be nil. - wantGroupID string - // wantCount is the expected Processor.Count after all calls (to verify SetCPUGroup doesn't clobber processor config). - wantCount uint32 - }{ - { - name: "set group on default processor", - groups: []*hcsschema.CpuGroup{{Id: "group-1"}}, - wantGroupID: "group-1", - }, - { - name: "nil clears group", - groups: []*hcsschema.CpuGroup{{Id: "group-1"}, nil}, - wantGroupID: "", - }, - { - name: "overwrite group", - groups: []*hcsschema.CpuGroup{{Id: "first"}, {Id: "second"}}, - wantGroupID: "second", - }, - { - name: "after SetProcessor", - processor: &hcsschema.VirtualMachineProcessor{Count: 4, Limit: 2500}, - groups: []*hcsschema.CpuGroup{{Id: "cg-after-set"}}, - wantGroupID: "cg-after-set", - wantCount: 4, - }, - { - name: "after nil SetProcessor (no-op)", - groups: []*hcsschema.CpuGroup{{Id: "safe-group"}}, - wantGroupID: "safe-group", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b, cs := newBuilder(t) - var p ProcessorOptions = b - - if tt.processor != nil { - p.SetProcessor(tt.processor) - } - - for _, g := range tt.groups { - p.SetCPUGroup(g) - } - - proc := cs.VirtualMachine.ComputeTopology.Processor - if tt.wantGroupID == "" { - if proc.CpuGroup != nil { - t.Fatalf("CpuGroup = %+v, want nil", proc.CpuGroup) - } - } else { - if proc.CpuGroup == nil { - t.Fatal("CpuGroup should not be nil") - } - if proc.CpuGroup.Id != tt.wantGroupID { - t.Fatalf("CpuGroup.Id = %q, want %q", proc.CpuGroup.Id, tt.wantGroupID) - } - } - - if tt.wantCount != 0 && proc.Count != tt.wantCount { - t.Fatalf("Processor.Count = %d, want %d", proc.Count, tt.wantCount) - } - }) - } -} diff --git a/internal/vm/builder/scsi.go b/internal/vm/builder/scsi.go deleted file mode 100644 index 5b9d439849..0000000000 --- a/internal/vm/builder/scsi.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/pkg/errors" -) - -func (uvmb *UtilityVM) AddSCSIController(id string) { - if uvmb.doc.VirtualMachine.Devices.Scsi == nil { - uvmb.doc.VirtualMachine.Devices.Scsi = make(map[string]hcsschema.Scsi) - } - uvmb.doc.VirtualMachine.Devices.Scsi[id] = hcsschema.Scsi{ - Attachments: make(map[string]hcsschema.Attachment), - } -} - -func (uvmb *UtilityVM) AddSCSIDisk(controller string, lun string, disk hcsschema.Attachment) error { - if uvmb.doc.VirtualMachine.Devices.Scsi == nil { - return errors.New("SCSI controller has not been added") - } - - ctrl, ok := uvmb.doc.VirtualMachine.Devices.Scsi[controller] - if !ok { - return errors.Errorf("no scsi controller with id %s found", controller) - } - - ctrl.Attachments[lun] = disk - - return nil -} diff --git a/internal/vm/builder/scsi_test.go b/internal/vm/builder/scsi_test.go deleted file mode 100644 index aebd584eee..0000000000 --- a/internal/vm/builder/scsi_test.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestSCSI(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - - if err := devices.AddSCSIDisk("0", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err == nil { - t.Fatal("AddSCSIDisk should fail when controller missing") - } - - devices.AddSCSIController("0") - if err := devices.AddSCSIDisk("0", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: true}); err != nil { - t.Fatalf("AddSCSIDisk error = %v", err) - } - - // Verify the attachment is reflected directly in the document (map reference semantics - - // no write-back of the Scsi struct copy is needed). - att := cs.VirtualMachine.Devices.Scsi["0"].Attachments["1"] - if att.Path != "disk.vhdx" || att.Type_ != "VirtualDisk" || !att.ReadOnly { - t.Fatal("SCSI attachment not applied as expected") - } - - // Add a second disk and confirm both attachments are present in the document, - // verifying that multiple calls also work correctly. - if err := devices.AddSCSIDisk("0", "2", hcsschema.Attachment{Path: "disk2.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err != nil { - t.Fatalf("AddSCSIDisk (lun 2) error = %v", err) - } - if len(cs.VirtualMachine.Devices.Scsi["0"].Attachments) != 2 { - t.Fatalf("expected 2 attachments, got %d", len(cs.VirtualMachine.Devices.Scsi["0"].Attachments)) - } - att2 := cs.VirtualMachine.Devices.Scsi["0"].Attachments["2"] - if att2.Path != "disk2.vhdx" || att2.ReadOnly { - t.Fatal("second SCSI attachment not applied as expected") - } - - if err := devices.AddSCSIDisk("missing", "1", hcsschema.Attachment{Path: "disk.vhdx", Type_: "VirtualDisk", ReadOnly: false}); err == nil { - t.Fatal("AddSCSIDisk should fail when controller does not exist") - } -} diff --git a/internal/vm/builder/storage.go b/internal/vm/builder/storage.go deleted file mode 100644 index 9d0e5ac7bd..0000000000 --- a/internal/vm/builder/storage.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -// StorageQoSOptions configures storage QoS settings for the Utility VM. -type StorageQoSOptions interface { - // SetStorageQoS sets storage related options for the Utility VM - SetStorageQoS(options *hcsschema.StorageQoS) -} - -var _ StorageQoSOptions = (*UtilityVM)(nil) - -func (uvmb *UtilityVM) SetStorageQoS(options *hcsschema.StorageQoS) { - if options == nil { - return - } - - if uvmb.doc.VirtualMachine.StorageQoS == nil { - uvmb.doc.VirtualMachine.StorageQoS = &hcsschema.StorageQoS{} - } - - uvmb.doc.VirtualMachine.StorageQoS.BandwidthMaximum = options.BandwidthMaximum - uvmb.doc.VirtualMachine.StorageQoS.IopsMaximum = options.IopsMaximum -} diff --git a/internal/vm/builder/storage_test.go b/internal/vm/builder/storage_test.go deleted file mode 100644 index 88b6ad7b89..0000000000 --- a/internal/vm/builder/storage_test.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestStorageQoS(t *testing.T) { - b, cs := newBuilder(t) - var storage StorageQoSOptions = b - - storage.SetStorageQoS(&hcsschema.StorageQoS{ - IopsMaximum: 1000, - BandwidthMaximum: 2000, - }) - if cs.VirtualMachine.StorageQoS == nil { - t.Fatal("StorageQoS should be initialized") - } - if cs.VirtualMachine.StorageQoS.IopsMaximum != 1000 || cs.VirtualMachine.StorageQoS.BandwidthMaximum != 2000 { - t.Fatal("StorageQoS not applied as expected") - } -} diff --git a/internal/vm/builder/vpmem.go b/internal/vm/builder/vpmem.go deleted file mode 100644 index c6d56d716e..0000000000 --- a/internal/vm/builder/vpmem.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/pkg/errors" -) - -func (uvmb *UtilityVM) AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) { - uvmb.doc.VirtualMachine.Devices.VirtualPMem = &hcsschema.VirtualPMemController{ - MaximumCount: maximumDevices, - MaximumSizeBytes: maximumSizeBytes, - Devices: make(map[string]hcsschema.VirtualPMemDevice), - } -} - -func (uvmb *UtilityVM) AddVPMemDevice(id string, device hcsschema.VirtualPMemDevice) error { - if uvmb.doc.VirtualMachine.Devices.VirtualPMem == nil { - return errors.New("VPMem controller has not been added") - } - uvmb.doc.VirtualMachine.Devices.VirtualPMem.Devices[id] = device - return nil -} diff --git a/internal/vm/builder/vpmem_test.go b/internal/vm/builder/vpmem_test.go deleted file mode 100644 index 4e681113b2..0000000000 --- a/internal/vm/builder/vpmem_test.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build windows - -package builder - -import ( - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestVPMem(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - if err := devices.AddVPMemDevice("0", hcsschema.VirtualPMemDevice{HostPath: "pmem.img", ReadOnly: true, ImageFormat: "raw"}); err == nil { - t.Fatal("AddVPMemDevice should fail when controller missing") - } - - devices.AddVPMemController(2, 1024) - if err := devices.AddVPMemDevice("0", hcsschema.VirtualPMemDevice{HostPath: "pmem.img", ReadOnly: true, ImageFormat: "raw"}); err != nil { - t.Fatalf("AddVPMemDevice error = %v", err) - } - - controller := cs.VirtualMachine.Devices.VirtualPMem - if controller.MaximumCount != 2 || controller.MaximumSizeBytes != 1024 { - t.Fatal("VPMem controller not applied as expected") - } - device := controller.Devices["0"] - if device.HostPath != "pmem.img" || !device.ReadOnly || device.ImageFormat != "raw" { - t.Fatal("VPMem device not applied as expected") - } -} diff --git a/internal/vm/builder/vsmb.go b/internal/vm/builder/vsmb.go deleted file mode 100644 index 5292bf3c7e..0000000000 --- a/internal/vm/builder/vsmb.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build windows - -package builder - -import ( - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - - "github.com/pkg/errors" -) - -func (uvmb *UtilityVM) AddVSMB(settings hcsschema.VirtualSmb) { - uvmb.doc.VirtualMachine.Devices.VirtualSmb = &settings -} - -func (uvmb *UtilityVM) AddVSMBShare(share hcsschema.VirtualSmbShare) error { - if uvmb.doc.VirtualMachine.Devices.VirtualSmb == nil { - return errors.New("VSMB has not been added") - } - - uvmb.doc.VirtualMachine.Devices.VirtualSmb.Shares = append( - uvmb.doc.VirtualMachine.Devices.VirtualSmb.Shares, - share, - ) - return nil -} diff --git a/internal/vm/builder/vsmb_test.go b/internal/vm/builder/vsmb_test.go deleted file mode 100644 index b528153f30..0000000000 --- a/internal/vm/builder/vsmb_test.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build windows - -package builder - -import ( - "reflect" - "testing" - - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -) - -func TestVSMB(t *testing.T) { - b, cs := newBuilder(t) - var devices DeviceOptions = b - opts := &hcsschema.VirtualSmbShareOptions{ReadOnly: true} - share1 := hcsschema.VirtualSmbShare{ - Name: "data", - Path: "C:\\share", - AllowedFiles: []string{"a.txt"}, - Options: opts, - } - share2 := hcsschema.VirtualSmbShare{ - Name: "data2", - Path: "C:\\share2", - AllowedFiles: []string{"b.txt"}, - Options: opts, - } - - devices.AddVSMB(hcsschema.VirtualSmb{DirectFileMappingInMB: 1024}) - - if err := devices.AddVSMBShare(share1); err != nil { - t.Fatalf("AddVSMBShare error = %v", err) - } - if err := devices.AddVSMBShare(share2); err != nil { - t.Fatalf("AddVSMBShare error = %v", err) - } - - vsmb := cs.VirtualMachine.Devices.VirtualSmb - if vsmb == nil || len(vsmb.Shares) != 2 { - t.Fatal("VSMB not configured as expected") - } - if vsmb.DirectFileMappingInMB != 1024 { - t.Fatalf("DirectFileMappingInMB = %d, want 1024", vsmb.DirectFileMappingInMB) - } - if !reflect.DeepEqual(vsmb.Shares[0], share1) || !reflect.DeepEqual(vsmb.Shares[1], share2) { - t.Fatal("VSMB shares not applied as expected") - } -} diff --git a/internal/vm/vmmanager/uvm.go b/internal/vm/vmmanager/uvm.go index 212eadff83..18868d368d 100644 --- a/internal/vm/vmmanager/uvm.go +++ b/internal/vm/vmmanager/uvm.go @@ -6,9 +6,9 @@ import ( "context" "github.com/Microsoft/hcsshim/internal/hcs" + 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/vm/builder" "github.com/Microsoft/go-winio/pkg/guid" "github.com/pkg/errors" @@ -25,14 +25,12 @@ type UtilityVM struct { cs *hcs.System } -// Create creates a new utility VM with the given ID and builder configuration. +// Create creates a new utility VM with the given ID and compute system configuration. // -// This method returns the concrete UtilityVM and Callers -// can use the interface views (for example, LifetimeManager, NetworkManager) -// as needed. This follows the "accept interfaces, return structs" convention. -func Create(ctx context.Context, id string, builder *builder.UtilityVM) (*UtilityVM, error) { - config := builder.Get() - +// This method returns the concrete UtilityVM. Callers +// can use the manager interfaces (for example, LifetimeManager, NetworkManager) +// as needed. +func Create(ctx context.Context, id string, config *hcsschema.ComputeSystem) (*UtilityVM, error) { cs, err := hcs.CreateComputeSystem(ctx, id, config) if err != nil { return nil, errors.Wrap(err, "failed to create compute system")