diff --git a/internal/vm/README.md b/internal/vm/README.md new file mode 100644 index 0000000000..18c7e17bd7 --- /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` + - Interface definitions and shared types. + - `UVM` interface exposes APIs for lifecycle, resources, and device management of running VM. + - `Builder` exposes APIs for shaping the configuration used to create a VM. +- `internal/vm/builder` + - 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` + - 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 index 288daad76a..81db54d4f8 100644 --- a/internal/vm/builder.go +++ b/internal/vm/builder.go @@ -1,74 +1,88 @@ package vm -import ( - "context" -) +import hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -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 +// Builder is the base builder interface for constructing Utility VMs. +// It provides access to various manager interfaces for configuring VM resources such as +// memory, processors, serial consoles, boot options, and storage QoS settings. +type Builder interface { + GuestOS() GuestOS + // Get returns the current config document being built, which can be used + // for creating or modifying a Utility VM. + Get() any + // Memory returns the MemoryManager for configuring VM memory settings. + Memory() MemoryManager + // Processor returns the ProcessorManager for configuring VM processor settings. + Processor() ProcessorManager + // Numa returns the NumaManager for configuring VM NUMA settings. + Numa() NumaManager + // Device returns the DeviceManager for configuring VM devices such as PCI and serial consoles. + Device() DeviceManager + // Boot returns the BootManager for configuring VM boot options. + Boot() BootManager + // StorageQoS returns the StorageQoSManager for configuring VM storage quality of service settings. + StorageQoS() StorageQoSManager } // 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 + SetMemoryLimit(memoryMB uint64) // 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 + SetMemoryConfig(config *MemoryConfig) // SetMMIOConfig sets memory mapped IO configurations for the Utility VM. - SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) error + SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) + // SetFirmwareFallbackMeasuredSlit sets the SLIT type as "FirmwareFallbackMeasured" for the Utility VM. + SetFirmwareFallbackMeasuredSlit() } // 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 + // SetProcessorConfig sets an array of different processor configuration options available. + SetProcessorConfig(config *ProcessorConfig) + // SetCPUGroup sets the CPU group that the Utility VM will belong to on a Windows host. + SetCPUGroup(id string) } -// SerialManager manages setting up serial consoles for the Utility VM. -type SerialManager interface { +type NumaManager 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) +} + +type DeviceManager interface { + // AddVPCIDevice adds a PCI device to the Utility VM. + // If the device is already added, we return an error. + AddVPCIDevice(device VPCIDeviceID, 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, path string, typ SCSIDiskType, readOnly bool) 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, path string, readOnly bool, imageFormat string) error + // AddVSMB adds a VSMB share to the Utility VM with the specified path, name, allowed file access, and options. + AddVSMB(path string, name string, allowed []string, options *hcsschema.VirtualSmbShareOptions) 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() } // 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 + SetUEFIBoot(dir string, path string, args string) // 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 +// 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) } - -// 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/hcs/boot.go b/internal/vm/builder/boot.go similarity index 84% rename from internal/vm/hcs/boot.go rename to internal/vm/builder/boot.go index a1794a4f2c..3ea21f79f0 100644 --- a/internal/vm/hcs/boot.go +++ b/internal/vm/builder/boot.go @@ -1,14 +1,19 @@ //go:build windows -package hcs +package builder import ( hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" "github.com/Microsoft/hcsshim/osversion" "github.com/pkg/errors" ) -func (uvmb *utilityVMBuilder) SetUEFIBoot(dir string, path string, args string) error { +func (uvmb *utilityVMBuilder) Boot() vm.BootManager { + return uvmb +} + +func (uvmb *utilityVMBuilder) SetUEFIBoot(dir string, path string, args string) { uvmb.doc.VirtualMachine.Chipset.Uefi = &hcsschema.Uefi{ BootThis: &hcsschema.UefiBootEntry{ DevicePath: path, @@ -17,7 +22,6 @@ func (uvmb *utilityVMBuilder) SetUEFIBoot(dir string, path string, args string) OptionalData: args, }, } - return nil } func (uvmb *utilityVMBuilder) SetLinuxKernelDirectBoot(kernel string, initRD string, cmd string) error { diff --git a/internal/vm/builder/boot_test.go b/internal/vm/builder/boot_test.go new file mode 100644 index 0000000000..0087521eed --- /dev/null +++ b/internal/vm/builder/boot_test.go @@ -0,0 +1,50 @@ +//go:build windows + +package builder + +import ( + "testing" + + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestBootConfig(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + b.Boot().SetUEFIBoot("root", "path", "args") + + if cs.VirtualMachine.Chipset.Uefi == nil || cs.VirtualMachine.Chipset.Uefi.BootThis == nil { + t.Fatal("UEFI boot not applied") + } + + boot := cs.VirtualMachine.Chipset.Uefi.BootThis + if boot.DevicePath != "path" { + t.Fatalf("UEFI DevicePath = %q, want %q", boot.DevicePath, "path") + } + if boot.DeviceType != "VmbFs" { + t.Fatalf("UEFI DeviceType = %q, want %q", boot.DeviceType, "VmbFs") + } + if boot.VmbFsRootPath != "root" { + t.Fatalf("UEFI VmbFsRootPath = %q, want %q", boot.VmbFsRootPath, "root") + } + if boot.OptionalData != "args" { + t.Fatalf("UEFI OptionalData = %q, want %q", boot.OptionalData, "args") + } + + err := b.Boot().SetLinuxKernelDirectBoot("kernel", "initrd", "cmd") + 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 new file mode 100644 index 0000000000..753f1166da --- /dev/null +++ b/internal/vm/builder/builder.go @@ -0,0 +1,68 @@ +//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" +) + +var _ vm.Builder = &utilityVMBuilder{} + +type utilityVMBuilder struct { + guestOS vm.GuestOS + doc *hcsschema.ComputeSystem + assignedDevices map[vm.VPCIDeviceID]*vPCIDevice +} + +func New(owner string, guestOS vm.GuestOS) (vm.Builder, 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, vm.ErrUnknownGuestOS + } + + return &utilityVMBuilder{ + guestOS: guestOS, + doc: doc, + assignedDevices: make(map[vm.VPCIDeviceID]*vPCIDevice), + }, nil +} + +func (uvmb *utilityVMBuilder) GuestOS() vm.GuestOS { + return uvmb.guestOS +} + +func (uvmb *utilityVMBuilder) Get() any { + return uvmb.doc +} diff --git a/internal/vm/builder/builder_test.go b/internal/vm/builder/builder_test.go new file mode 100644 index 0000000000..fdb5eff836 --- /dev/null +++ b/internal/vm/builder/builder_test.go @@ -0,0 +1,48 @@ +//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) (vm.Builder, *hcsschema.ComputeSystem) { + t.Helper() + b, err := New("owner", guestOS) + if err != nil { + t.Fatalf("New() error = %v", err) + } + cs, ok := b.Get().(*hcsschema.ComputeSystem) + if !ok { + t.Fatalf("builder.Get() type = %T, want *hcsschema.ComputeSystem", b.Get()) + } + return b, cs +} + +func TestNewBuilder_InvalidGuestOS(t *testing.T) { + if _, err := New("owner", vm.GuestOS("unknown")); !errors.Is(err, vm.ErrUnknownGuestOS) { + t.Fatalf("New() error = %v, want %v", err, vm.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.go b/internal/vm/builder/device.go new file mode 100644 index 0000000000..56b6860ac1 --- /dev/null +++ b/internal/vm/builder/device.go @@ -0,0 +1,88 @@ +//go:build windows + +package builder + +import ( + "strconv" + "strings" + + "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" +) + +// 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 +} + +func (uvmb *utilityVMBuilder) Device() vm.DeviceManager { + return uvmb +} + +func (uvmb *utilityVMBuilder) AddVPCIDevice(device vm.VPCIDeviceID, numaAffinity bool) error { + _, ok := uvmb.assignedDevices[device] + if ok { + return errors.Wrapf(vm.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{ + { + DeviceInstancePath: device.DeviceInstanceID, + VirtualFunction: device.VirtualFunctionIndex, + }, + }, + PropagateNumaAffinity: propagateAffinity, + } + + uvmb.assignedDevices[device] = &vPCIDevice{ + vmbusGUID: vmbusGUID.String(), + deviceInstanceID: device.DeviceInstanceID, + virtualFunctionIndex: device.VirtualFunctionIndex, + refCount: 1, + } + + return nil +} + +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 +} + +func (uvmb *utilityVMBuilder) 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/device_test.go b/internal/vm/builder/device_test.go new file mode 100644 index 0000000000..3399bef1dc --- /dev/null +++ b/internal/vm/builder/device_test.go @@ -0,0 +1,59 @@ +//go:build windows + +package builder + +import ( + "strconv" + "testing" + + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +func TestVPCIDevice(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + device := vm.VPCIDeviceID{DeviceInstanceID: "PCI\\VEN_1234", VirtualFunctionIndex: 2} + + if err := b.Device().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.DeviceInstanceID || entry.Functions[0].VirtualFunction != device.VirtualFunctionIndex { + t.Fatal("VPCI function not applied as expected") + } + if entry.PropagateNumaAffinity == nil || !*entry.PropagateNumaAffinity { + t.Fatal("PropagateNumaAffinity should be true") + } + } + + if err := b.Device().AddVPCIDevice(device, false); !errors.Is(err, vm.ErrAlreadySet) { + t.Fatalf("AddVPCIDevice duplicate error = %v, want %v", err, vm.ErrAlreadySet) + } +} + +func TestSerialConsoleAndGraphics(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + if err := b.Device().SetSerialConsole(1, "not-a-pipe"); err == nil { + t.Fatal("SetSerialConsole should reject non-pipe path") + } + + pipePath := `\\.\pipe\serial` + if err := b.Device().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") + } + + b.Device().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/hcs/memory.go b/internal/vm/builder/memory.go similarity index 60% rename from internal/vm/hcs/memory.go rename to internal/vm/builder/memory.go index 5934d470fc..c126dfcceb 100644 --- a/internal/vm/hcs/memory.go +++ b/internal/vm/builder/memory.go @@ -1,30 +1,37 @@ //go:build windows -package hcs +package builder import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/vm" ) -func (uvmb *utilityVMBuilder) SetMemoryLimit(memoryMB uint64) error { +func (uvmb *utilityVMBuilder) Memory() vm.MemoryManager { + return uvmb +} + +func (uvmb *utilityVMBuilder) SetMemoryLimit(memoryMB uint64) { uvmb.doc.VirtualMachine.ComputeTopology.Memory.SizeInMB = memoryMB - return nil } -func (uvmb *utilityVMBuilder) SetMemoryConfig(config *vm.MemoryConfig) error { +func (uvmb *utilityVMBuilder) SetMemoryConfig(config *vm.MemoryConfig) { 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 { +func (uvmb *utilityVMBuilder) SetMMIOConfig(lowGapMB uint64, highBaseMB uint64, highGapMB uint64) { memory := uvmb.doc.VirtualMachine.ComputeTopology.Memory memory.LowMMIOGapInMB = lowGapMB memory.HighMMIOBaseInMB = highBaseMB memory.HighMMIOGapInMB = highGapMB - return nil +} + +func (uvmb *utilityVMBuilder) 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 new file mode 100644 index 0000000000..501fa65c8e --- /dev/null +++ b/internal/vm/builder/memory_test.go @@ -0,0 +1,52 @@ +//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) + b.Memory().SetMemoryLimit(512) + if cs.VirtualMachine.ComputeTopology.Memory.SizeInMB != 512 { + t.Fatalf("SizeInMB = %d, want %d", cs.VirtualMachine.ComputeTopology.Memory.SizeInMB, 512) + } + + b.Memory().SetMemoryConfig(&vm.MemoryConfig{ + BackingType: vm.MemoryBackingTypeVirtual, + DeferredCommit: true, + HotHint: true, + ColdHint: false, + ColdDiscardHint: true, + }) + mem := cs.VirtualMachine.ComputeTopology.Memory + if !mem.AllowOvercommit || !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { + t.Fatal("memory config not applied as expected") + } + + b.Memory().SetMemoryConfig(&vm.MemoryConfig{ + BackingType: vm.MemoryBackingTypePhysical, + DeferredCommit: true, + HotHint: true, + ColdHint: false, + ColdDiscardHint: true, + }) + mem = cs.VirtualMachine.ComputeTopology.Memory + if mem.AllowOvercommit || !mem.EnableDeferredCommit || !mem.EnableHotHint || mem.EnableColdHint || !mem.EnableColdDiscardHint { + t.Fatal("memory config not applied as expected") + } + + b.Memory().SetMMIOConfig(64, 128, 256) + if mem.LowMMIOGapInMB != 64 || mem.HighMMIOBaseInMB != 128 || mem.HighMMIOGapInMB != 256 { + t.Fatal("MMIO config not applied as expected") + } + + b.Memory().SetFirmwareFallbackMeasuredSlit() + if mem.SlitType == nil || *mem.SlitType != hcsschema.VirtualSlitType_FIRMWARE_FALLBACK_MEASURED { + t.Fatal("SlitType not set to FIRMWARE_FALLBACK_MEASURED") + } +} diff --git a/internal/vm/builder/numa.go b/internal/vm/builder/numa.go new file mode 100644 index 0000000000..8921afb8a4 --- /dev/null +++ b/internal/vm/builder/numa.go @@ -0,0 +1,20 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func (uvmb *utilityVMBuilder) Numa() vm.NumaManager { + return uvmb +} + +func (uvmb *utilityVMBuilder) SetNUMAProcessorsSettings(numaProcessors *hcsschema.NumaProcessors) { + uvmb.doc.VirtualMachine.ComputeTopology.Processor.NumaProcessorsSettings = numaProcessors +} + +func (uvmb *utilityVMBuilder) 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 new file mode 100644 index 0000000000..8693993c03 --- /dev/null +++ b/internal/vm/builder/numa_test.go @@ -0,0 +1,96 @@ +//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) + 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, + } + + b.Numa().SetNUMAProcessorsSettings(numaProcessors) + b.Numa().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 new file mode 100644 index 0000000000..a7f75e87b7 --- /dev/null +++ b/internal/vm/builder/processor.go @@ -0,0 +1,23 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func (uvmb *utilityVMBuilder) Processor() vm.ProcessorManager { + return uvmb +} + +func (uvmb *utilityVMBuilder) SetProcessorConfig(config *vm.ProcessorConfig) { + processor := uvmb.doc.VirtualMachine.ComputeTopology.Processor + processor.Count = config.Count + processor.Limit = config.Limit + processor.Weight = config.Weight +} + +func (uvmb *utilityVMBuilder) SetCPUGroup(id string) { + uvmb.doc.VirtualMachine.ComputeTopology.Processor.CpuGroup = &hcsschema.CpuGroup{Id: id} +} diff --git a/internal/vm/builder/processor_test.go b/internal/vm/builder/processor_test.go new file mode 100644 index 0000000000..fcc0d6cc39 --- /dev/null +++ b/internal/vm/builder/processor_test.go @@ -0,0 +1,23 @@ +//go:build windows + +package builder + +import ( + "testing" + + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestProcessorConfigAndCPUGroup(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + b.Processor().SetProcessorConfig(&vm.ProcessorConfig{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") + } + + b.Processor().SetCPUGroup("cg1") + if proc.CpuGroup == nil || proc.CpuGroup.Id != "cg1" { + t.Fatal("cpu group not applied as expected") + } +} diff --git a/internal/vm/builder/scsi.go b/internal/vm/builder/scsi.go new file mode 100644 index 0000000000..656a886f6f --- /dev/null +++ b/internal/vm/builder/scsi.go @@ -0,0 +1,43 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +func (uvmb *utilityVMBuilder) 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 *utilityVMBuilder) AddSCSIDisk(controller string, lun string, path string, typ vm.SCSIDiskType, readOnly bool) 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) + } + + typeString := typ.String() + if typeString == "" { + return errors.Errorf("unsupported SCSI disk type %d", typ) + } + + ctrl.Attachments[lun] = hcsschema.Attachment{ + Path: path, + Type_: typeString, + ReadOnly: readOnly, + } + 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 new file mode 100644 index 0000000000..8caa55802f --- /dev/null +++ b/internal/vm/builder/scsi_test.go @@ -0,0 +1,35 @@ +//go:build windows + +package builder + +import ( + "testing" + + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestSCSI(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + + if err := b.Device().AddSCSIDisk("0", "1", "disk.vhdx", vm.SCSIDiskTypeVirtualDisk, false); err == nil { + t.Fatal("AddSCSIDisk should fail when controller missing") + } + + b.Device().AddSCSIController("0") + if err := b.Device().AddSCSIDisk("0", "1", "disk.vhdx", vm.SCSIDiskTypeVirtualDisk, 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_ != vm.SCSIDiskTypeVirtualDisk.String() || !att.ReadOnly { + t.Fatal("SCSI attachment not applied as expected") + } + + if err := b.Device().AddSCSIDisk("0", "2", "disk.vhdx", vm.SCSIDiskType(99), false); err == nil { + t.Fatal("AddSCSIDisk should reject unsupported disk type") + } + if err := b.Device().AddSCSIDisk("missing", "1", "disk.vhdx", vm.SCSIDiskTypeVirtualDisk, 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 new file mode 100644 index 0000000000..a5d0031e2d --- /dev/null +++ b/internal/vm/builder/storage.go @@ -0,0 +1,21 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/vm" +) + +func (uvmb *utilityVMBuilder) StorageQoS() vm.StorageQoSManager { + return uvmb +} + +func (uvmb *utilityVMBuilder) SetStorageQoS(iopsMaximum int64, bandwidthMaximum int64) { + if uvmb.doc.VirtualMachine.StorageQoS == nil { + uvmb.doc.VirtualMachine.StorageQoS = &hcsschema.StorageQoS{} + } + + uvmb.doc.VirtualMachine.StorageQoS.BandwidthMaximum = int32(bandwidthMaximum) + uvmb.doc.VirtualMachine.StorageQoS.IopsMaximum = int32(iopsMaximum) +} diff --git a/internal/vm/builder/storage_test.go b/internal/vm/builder/storage_test.go new file mode 100644 index 0000000000..090221071f --- /dev/null +++ b/internal/vm/builder/storage_test.go @@ -0,0 +1,20 @@ +//go:build windows + +package builder + +import ( + "testing" + + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestStorageQoS(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + b.StorageQoS().SetStorageQoS(1000, 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 new file mode 100644 index 0000000000..4d744f06b3 --- /dev/null +++ b/internal/vm/builder/vpmem.go @@ -0,0 +1,28 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/pkg/errors" +) + +func (uvmb *utilityVMBuilder) AddVPMemController(maximumDevices uint32, maximumSizeBytes uint64) { + uvmb.doc.VirtualMachine.Devices.VirtualPMem = &hcsschema.VirtualPMemController{ + MaximumCount: maximumDevices, + MaximumSizeBytes: maximumSizeBytes, + Devices: make(map[string]hcsschema.VirtualPMemDevice), + } +} + +func (uvmb *utilityVMBuilder) AddVPMemDevice(id string, path string, readOnly bool, imageFormat string) error { + if uvmb.doc.VirtualMachine.Devices.VirtualPMem == nil { + return errors.New("VPMem controller has not been added") + } + uvmb.doc.VirtualMachine.Devices.VirtualPMem.Devices[id] = hcsschema.VirtualPMemDevice{ + HostPath: path, + ReadOnly: readOnly, + ImageFormat: imageFormat, + } + return nil +} diff --git a/internal/vm/builder/vpmem_test.go b/internal/vm/builder/vpmem_test.go new file mode 100644 index 0000000000..8b06025c21 --- /dev/null +++ b/internal/vm/builder/vpmem_test.go @@ -0,0 +1,30 @@ +//go:build windows + +package builder + +import ( + "testing" + + "github.com/Microsoft/hcsshim/internal/vm" +) + +func TestVPMem(t *testing.T) { + b, cs := newBuilder(t, vm.Linux) + if err := b.Device().AddVPMemDevice("0", "pmem.img", true, "raw"); err == nil { + t.Fatal("AddVPMemDevice should fail when controller missing") + } + + b.Device().AddVPMemController(2, 1024) + if err := b.Device().AddVPMemDevice("0", "pmem.img", true, "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 new file mode 100644 index 0000000000..e0fb1e6157 --- /dev/null +++ b/internal/vm/builder/vsmb.go @@ -0,0 +1,22 @@ +//go:build windows + +package builder + +import ( + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" +) + +func (uvmb *utilityVMBuilder) AddVSMB(path string, name string, allowed []string, options *hcsschema.VirtualSmbShareOptions) 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: options, + }, + }, + } + return nil +} diff --git a/internal/vm/builder/vsmb_test.go b/internal/vm/builder/vsmb_test.go new file mode 100644 index 0000000000..619012f221 --- /dev/null +++ b/internal/vm/builder/vsmb_test.go @@ -0,0 +1,28 @@ +//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) + opts := &hcsschema.VirtualSmbShareOptions{ReadOnly: true} + if err := b.Device().AddVSMB("C:\\share", "data", []string{"a.txt"}, opts); err != nil { + t.Fatalf("AddVSMB error = %v", err) + } + + vsmb := cs.VirtualMachine.Devices.VirtualSmb + if vsmb == nil || len(vsmb.Shares) != 1 { + t.Fatal("VSMB not configured as expected") + } + share := vsmb.Shares[0] + if share.Path != "C:\\share" || share.Name != "data" || !reflect.DeepEqual(share.AllowedFiles, []string{"a.txt"}) || share.Options != opts { + t.Fatal("VSMB share not applied as expected") + } +} diff --git a/internal/vm/builder_types.go b/internal/vm/builder_types.go new file mode 100644 index 0000000000..e019f54cb7 --- /dev/null +++ b/internal/vm/builder_types.go @@ -0,0 +1,46 @@ +package vm + +import "github.com/pkg/errors" + +var ( + ErrAlreadySet = errors.New("field has already been set") + ErrUnknownGuestOS = errors.New("unknown guest operating system supplied") +) + +// GuestOS signifies the guest operating system that a Utility VM will be running. +type GuestOS string + +const ( + Windows GuestOS = "windows" + Linux GuestOS = "linux" +) + +// MemoryBackingType represents the type of memory backing that should be used for a Utility VM. +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 +} + +// ProcessorConfig holds the processor options that should be configurable for a Utility VM. +type ProcessorConfig struct { + Count uint32 + Limit uint64 + Weight uint64 +} + +// VPCIDeviceID represents the unique identifier of a PCI device in a VM. +type VPCIDeviceID struct { + DeviceInstanceID string + VirtualFunctionIndex uint16 +} 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/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..ef22c14700 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -2,17 +2,11 @@ package vm import ( "context" - "errors" "net" - "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" -) + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" -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") + "github.com/Microsoft/go-winio/pkg/guid" ) // UVM is an abstraction around a lightweight virtual machine. It houses core lifecycle methods such as Create @@ -22,12 +16,55 @@ type UVM 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() GuestOS + + // LifetimeManager returns an interface for managing the lifecycle of the Utility VM. + LifetimeManager() LifetimeManager + + // NetworkManager returns an interface for managing network adapters for the Utility VM. + NetworkManager() NetworkManager + + // ResourceManager returns an interface for managing resource configuration for the Utility VM. + ResourceManager() ResourceManager + + // SCSIManager returns an interface for managing SCSI devices for the Utility VM. + SCSIManager() SCSIManager + + // PCIManager returns an interface for managing PCI devices for the Utility VM. + PCIManager() PCIManager + + // VPMemManager returns an interface for managing virtual persistent memory devices for the Utility VM. + VPMemManager() VPMemManager + + // VSMBManager returns an interface for managing virtual smb shares for the Utility VM. + VSMBManager() VSMBManager + + // VMSocketManager returns an interface for managing socket transports for the Utility VM. + VMSocketManager() VMSocketManager + + // PipeManager returns an interface for managing named pipes for the Utility VM. + PipeManager() PipeManager + + // Plan9Manager returns an interface for managing Plan 9 shares for the Utility VM. + Plan9Manager() Plan9Manager +} + +type LifetimeManager interface { // 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 + // 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 @@ -41,92 +78,48 @@ type UVM interface { // 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) + // Wait synchronously waits for the Utility VM to terminate. + Wait(ctx context.Context) error - // Supported returns if the virt stack supports a given operation on a resource. - Supported(resource Resource, operation ResourceOperation) bool + // 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 } -// 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 -) +type ResourceManager interface { + // SetCPUGroup assigns the Utility VM to a cpu group. + SetCPUGroup(ctx context.Context, cpuGroup string) error -// Operation refers to the type of operation to perform on a given resource. -type ResourceOperation uint8 - -const ( - Add ResourceOperation = iota - Remove - Update -) - -// GuestOS signifies the guest operating system that a Utility VM will be running. -type GuestOS string - -const ( - Windows GuestOS = "windows" - Linux GuestOS = "linux" -) + // 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, limit, weight, reservation uint64, maximumFrequencyMHz uint32) error -// 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 -) + // UpdateMemory makes a call to the VM's orchestrator to update the VM's size in MB + UpdateMemory(ctx context.Context, memory uint64) error +} // 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 + // AddSCSIDisk hot adds a SCSI disk to the Utility VM. + AddSCSIDisk(ctx context.Context, disk SCSIDisk) error + // RemoveSCSIDisk removes a SCSI disk from a Utility VM. - RemoveSCSIDisk(ctx context.Context, controller uint32, lun uint32, path string) error + RemoveSCSIDisk(ctx context.Context, disk SCSIDisk) 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 + AddVPMemDevice(ctx context.Context, id uint32, path string, readOnly bool, imageFormat string) error + + // RemoveVPMemDevice removes a virtual pmem device from the Utility VM. + RemoveVPMemDevice(ctx context.Context, id uint32) error } // NetworkManager manages adding and removing network adapters for a Utility VM. @@ -134,52 +127,43 @@ 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 + + // UpdateNIC updates the configuration of a network adapter on the Utility VM. + UpdateNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) 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 -} + AddDevice(ctx context.Context, instanceID string, index uint16, vmbusGUID string) error -// VMSocketType refers to which hypervisor socket transport type to use. -type VMSocketType uint8 - -const ( - HvSocket VMSocketType = iota - VSock -) + // RemoveDevice removes the pci device identified by `vmbusGUID` from the Utility VM. + RemoveDevice(ctx context.Context, vmbusGUID string) error +} // 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) -} + // VMSocketListen will create the requested HvSocket and listen on the address specified by `connID`. + VMSocketListen(ctx context.Context, connID interface{}) (net.Listener, error) + + // UpdateHvSocketService will update the configuration for the HvSocket service with the specified `serviceID`. + UpdateHvSocketService(ctx context.Context, serviceID string, config interface{}) error -// VSMBOptions -type VSMBOptions struct { - ReadOnly bool - CacheIo bool - NoDirectMap bool - PseudoOplocks bool - ShareRead bool - TakeBackupPrivilege bool - NoOplocks bool - PseudoDirnotify bool + // RemoveHvSocketService will remove the HvSocket service with the specified `serviceID` from the Utility VM. + RemoveHvSocketService(ctx context.Context, serviceID string) error } // 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 + AddVSMB(ctx context.Context, hostPath string, name string, allowedFiles []string, options *hcsschema.VirtualSmbShareOptions) error + // RemoveVSMB removes a virtual smb share from a running Utility VM. RemoveVSMB(ctx context.Context, name string) error } @@ -188,6 +172,16 @@ type VSMBManager interface { 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 } + +// 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 +} diff --git a/internal/vm/vm_types.go b/internal/vm/vm_types.go new file mode 100644 index 0000000000..7585f643b3 --- /dev/null +++ b/internal/vm/vm_types.go @@ -0,0 +1,41 @@ +package vm + +// SCSIDiskType refers to the disk type of the scsi device. +type SCSIDiskType uint8 + +const ( + SCSIDiskTypeVirtualDisk SCSIDiskType = iota + SCSIDiskTypeEVD + SCSIDiskTypePassThrough + // Other types supported maybe Iso, SharedDisk, VirtualFloppyDisk. + // For now, we only support the above 3 types from shim. +) + +func (s SCSIDiskType) String() string { + switch s { + case SCSIDiskTypeVirtualDisk: + return "VirtualDisk" + case SCSIDiskTypeEVD: + return "ExtensibleVirtualDisk" + case SCSIDiskTypePassThrough: + return "PassThru" + default: + return "" + } +} + +// SCSIDisk represents the disk information of a scsi device. +type SCSIDisk struct { + // Controller is the scsi controller number of the scsi device. + Controller uint + // LUN is the scsi LUN number of the scsi device. + LUN uint + // Path is the path to the disk. + Path string + // Type is the disk type of the scsi device. + Type SCSIDiskType + // ReadOnly indicates whether the scsi disk is read only or not. + ReadOnly bool + // ExtensibleVirtualDiskType is the disk type of the extensible virtual disk. + ExtensibleVirtualDiskType string +} diff --git a/internal/vm/vmmanager/hcs.go b/internal/vm/vmmanager/hcs.go new file mode 100644 index 0000000000..d2faa3c268 --- /dev/null +++ b/internal/vm/vmmanager/hcs.go @@ -0,0 +1,148 @@ +//go:build windows + +package vmmanager + +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" + + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var _ vm.UVM = &utilityVM{} + +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. +func Create(ctx context.Context, id string, builder vm.Builder) (vm.UVM, error) { + config, ok := builder.Get().(*hcsschema.ComputeSystem) + if !ok { + return nil, errors.New("uvm builder returned non-hcsschema config") + } + + 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 +} + +// LifetimeManager returns the VM manager associated with the utility VM. +func (uvm *utilityVM) LifetimeManager() vm.LifetimeManager { + return uvm +} + +// 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) 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 +} + +// 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) +} + +// 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/hcs/network.go b/internal/vm/vmmanager/network.go similarity index 68% rename from internal/vm/hcs/network.go rename to internal/vm/vmmanager/network.go index f61f6bb4ed..190f536b62 100644 --- a/internal/vm/hcs/network.go +++ b/internal/vm/vmmanager/network.go @@ -1,6 +1,6 @@ //go:build windows -package hcs +package vmmanager import ( "context" @@ -9,8 +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/internal/vm" ) +func (uvm *utilityVM) NetworkManager() vm.NetworkManager { + return uvm +} + func (uvm *utilityVM) AddNIC(ctx context.Context, nicID, endpointID, macAddr string) error { request := hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeAdd, @@ -34,3 +39,12 @@ func (uvm *utilityVM) RemoveNIC(ctx context.Context, nicID, endpointID, macAddr } return uvm.cs.Modify(ctx, request) } + +func (uvm *utilityVM) UpdateNIC(ctx context.Context, nicID string, settings *hcsschema.NetworkAdapter) error { + req := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeUpdate, + ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, nicID), + Settings: settings, + } + return uvm.cs.Modify(ctx, req) +} diff --git a/internal/vm/hcs/pci.go b/internal/vm/vmmanager/pci.go similarity index 74% rename from internal/vm/hcs/pci.go rename to internal/vm/vmmanager/pci.go index 14c4eb1b05..88f6c17c17 100644 --- a/internal/vm/hcs/pci.go +++ b/internal/vm/vmmanager/pci.go @@ -1,6 +1,6 @@ //go:build windows -package hcs +package vmmanager import ( "context" @@ -9,10 +9,15 @@ 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/internal/vm" "github.com/Microsoft/hcsshim/osversion" ) -func (uvm *utilityVM) AddDevice(ctx context.Context, instanceID, vmbusGUID string) error { +func (uvm *utilityVM) PCIManager() vm.PCIManager { + return uvm +} + +func (uvm *utilityVM) AddDevice(ctx context.Context, instanceID string, index uint16, vmbusGUID string) error { var propagateAffinity *bool T := true if osversion.Get().Build >= osversion.V25H1Server { @@ -25,6 +30,7 @@ func (uvm *utilityVM) AddDevice(ctx context.Context, instanceID, vmbusGUID strin Functions: []hcsschema.VirtualPciFunction{ { DeviceInstancePath: instanceID, + VirtualFunction: index, }, }, PropagateNumaAffinity: propagateAffinity, @@ -33,7 +39,7 @@ func (uvm *utilityVM) AddDevice(ctx context.Context, instanceID, vmbusGUID strin return uvm.cs.Modify(ctx, request) } -func (uvm *utilityVM) RemoveDevice(ctx context.Context, instanceID, vmbusGUID string) error { +func (uvm *utilityVM) RemoveDevice(ctx context.Context, vmbusGUID string) error { request := &hcsschema.ModifySettingRequest{ ResourcePath: fmt.Sprintf(resourcepaths.VirtualPCIResourceFormat, vmbusGUID), RequestType: guestrequest.RequestTypeRemove, diff --git a/internal/vm/vmmanager/pipe.go b/internal/vm/vmmanager/pipe.go new file mode 100644 index 0000000000..7568a3794e --- /dev/null +++ b/internal/vm/vmmanager/pipe.go @@ -0,0 +1,42 @@ +//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/internal/vm" + "github.com/pkg/errors" +) + +func (uvm *utilityVM) PipeManager() vm.PipeManager { + return uvm +} + +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/hcs/plan9.go b/internal/vm/vmmanager/plan9.go similarity index 89% rename from internal/vm/hcs/plan9.go rename to internal/vm/vmmanager/plan9.go index 23cd7cc030..08955ee7db 100644 --- a/internal/vm/hcs/plan9.go +++ b/internal/vm/vmmanager/plan9.go @@ -1,6 +1,6 @@ //go:build windows -package hcs +package vmmanager import ( "context" @@ -8,8 +8,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/internal/vm" ) +func (uvm *utilityVM) Plan9Manager() vm.Plan9Manager { + return uvm +} + func (uvm *utilityVM) AddPlan9(ctx context.Context, path, name string, port int32, flags int32, allowed []string) error { modification := &hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeAdd, diff --git a/internal/vm/vmmanager/properties.go b/internal/vm/vmmanager/properties.go new file mode 100644 index 0000000000..8bf7d11f34 --- /dev/null +++ b/internal/vm/vmmanager/properties.go @@ -0,0 +1,20 @@ +//go:build windows + +package vmmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/pkg/errors" +) + +// 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 +} diff --git a/internal/vm/vmmanager/resources.go b/internal/vm/vmmanager/resources.go new file mode 100644 index 0000000000..226d20b7c4 --- /dev/null +++ b/internal/vm/vmmanager/resources.go @@ -0,0 +1,56 @@ +//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/vm" + "github.com/pkg/errors" +) + +func (uvm *utilityVM) ResourceManager() vm.ResourceManager { + return uvm +} + +func (uvm *utilityVM) SetCPUGroup(ctx context.Context, cpuGroup string) error { + modification := &hcsschema.ModifySettingRequest{ + ResourcePath: resourcepaths.CPUGroupResourcePath, + Settings: &hcsschema.CpuGroup{ + Id: cpuGroup, + }, + } + 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, limit, weight, reservation uint64, maximumFrequencyMHz uint32) error { + modification := &hcsschema.ModifySettingRequest{ + ResourcePath: resourcepaths.CPULimitsResourcePath, + Settings: &hcsschema.ProcessorLimits{ + Limit: limit, + Weight: weight, + Reservation: reservation, + MaximumFrequencyMHz: maximumFrequencyMHz, + }, + } + 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..df411b65b6 --- /dev/null +++ b/internal/vm/vmmanager/scsi.go @@ -0,0 +1,46 @@ +//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/internal/vm" + "github.com/pkg/errors" +) + +func (uvm *utilityVM) SCSIManager() vm.SCSIManager { + return uvm +} + +func (uvm *utilityVM) AddSCSIDisk(ctx context.Context, disk vm.SCSIDisk) error { + typString := disk.Type.String() + if typString == "" { + return errors.Errorf("unsupported SCSI disk type %d", disk.Type) + } + + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + Settings: hcsschema.Attachment{ + Path: disk.Path, + Type_: typString, + ReadOnly: disk.ReadOnly, + ExtensibleVirtualDiskType: disk.ExtensibleVirtualDiskType, + }, + ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, guestrequest.ScsiControllerGuids[disk.Controller], disk.LUN), + } + return uvm.cs.Modify(ctx, request) +} + +func (uvm *utilityVM) RemoveSCSIDisk(ctx context.Context, disk vm.SCSIDisk) error { + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.SCSIResourceFormat, guestrequest.ScsiControllerGuids[disk.Controller], disk.LUN), + } + + return uvm.cs.Modify(ctx, request) +} diff --git a/internal/vm/vmmanager/vmsocket.go b/internal/vm/vmmanager/vmsocket.go new file mode 100644 index 0000000000..9b9e7a1caa --- /dev/null +++ b/internal/vm/vmmanager/vmsocket.go @@ -0,0 +1,68 @@ +//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/hcsshim/internal/vm" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/go-winio/pkg/guid" + "github.com/pkg/errors" +) + +func (uvm *utilityVM) VMSocketManager() vm.VMSocketManager { + return uvm +} + +func (uvm *utilityVM) VMSocketListen(_ context.Context, connID interface{}) (net.Listener, error) { + serviceGUID, ok := connID.(guid.GUID) + if !ok { + return nil, errors.New("parameter passed to hvsocketlisten is not a GUID") + } + + return winio.ListenHvsock(&winio.HvsockAddr{ + VMID: uvm.vmID, + ServiceID: serviceGUID, + }) +} + +// 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 interface{}) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeUpdate, + ResourcePath: fmt.Sprintf(resourcepaths.HvSocketConfigResourceFormat, serviceID), + Settings: config, + } + return uvm.cs.Modify(ctx, modification) +} + +// 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), + } + return uvm.cs.Modify(ctx, modification) +} diff --git a/internal/vm/vmmanager/vpmem.go b/internal/vm/vmmanager/vpmem.go new file mode 100644 index 0000000000..9a193a0871 --- /dev/null +++ b/internal/vm/vmmanager/vpmem.go @@ -0,0 +1,38 @@ +//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/internal/vm" +) + +func (uvm *utilityVM) VPMemManager() vm.VPMemManager { + return uvm +} + +func (uvm *utilityVM) AddVPMemDevice(ctx context.Context, id uint32, path string, readOnly bool, imageFormat string) error { + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + Settings: hcsschema.VirtualPMemDevice{ + HostPath: path, + ReadOnly: readOnly, + ImageFormat: imageFormat, + }, + ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, id), + } + return uvm.cs.Modify(ctx, request) +} + +func (uvm *utilityVM) RemoveVPMemDevice(ctx context.Context, id uint32) error { + request := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeRemove, + ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, id), + } + return uvm.cs.Modify(ctx, request) +} diff --git a/internal/vm/vmmanager/vsmb.go b/internal/vm/vmmanager/vsmb.go new file mode 100644 index 0000000000..b4ef8ba693 --- /dev/null +++ b/internal/vm/vmmanager/vsmb.go @@ -0,0 +1,39 @@ +//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/Microsoft/hcsshim/internal/vm" +) + +func (uvm *utilityVM) VSMBManager() vm.VSMBManager { + return uvm +} + +func (uvm *utilityVM) AddVSMB(ctx context.Context, path string, name string, allowed []string, options *hcsschema.VirtualSmbShareOptions) error { + modification := &hcsschema.ModifySettingRequest{ + RequestType: guestrequest.RequestTypeAdd, + Settings: hcsschema.VirtualSmbShare{ + Name: name, + Options: 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) +}