From e9472e00623f216633c95ed028af554dd2656883 Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Mon, 6 Apr 2026 11:36:03 +0530 Subject: [PATCH 1/2] [shimV2] remove all interfaces from vm manager and guest manager Following the shim V2 standard, we are declaring the interfaces at the place where the APIs are used. Therefore, we are deleting all the interfaces from the existing packages. Signed-off-by: Harsh Rawat --- internal/vm/guestmanager/block_cims.go | 10 ---- .../vm/guestmanager/combinedlayers_lcow.go | 10 ---- .../vm/guestmanager/combinedlayers_wcow.go | 14 ----- internal/vm/guestmanager/guest.go | 26 ++++++---- internal/vm/guestmanager/hvsocket.go | 7 --- internal/vm/guestmanager/manager.go | 25 --------- .../vm/guestmanager/mapped_directory_lcow.go | 10 ---- .../vm/guestmanager/mapped_directory_wcow.go | 8 --- internal/vm/guestmanager/security_policy.go | 10 ---- internal/vm/vmmanager/lifetime.go | 51 ------------------- internal/vm/vmmanager/pipe.go | 11 ---- internal/vm/vmmanager/resources.go | 17 ------- internal/vm/vmmanager/utils.go | 11 +++- internal/vm/vmmanager/vmsocket.go | 12 ----- internal/vm/vmmanager/vsmb.go | 11 ---- 15 files changed, 26 insertions(+), 207 deletions(-) diff --git a/internal/vm/guestmanager/block_cims.go b/internal/vm/guestmanager/block_cims.go index 022829df9c..33bc741773 100644 --- a/internal/vm/guestmanager/block_cims.go +++ b/internal/vm/guestmanager/block_cims.go @@ -11,16 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// CIMsManager exposes guest WCOW block CIM operations. -type CIMsManager interface { - // AddWCOWBlockCIMs adds WCOW block CIM mounts in the guest. - AddWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error - // RemoveWCOWBlockCIMs removes WCOW block CIM mounts from the guest. - RemoveWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error -} - -var _ CIMsManager = (*Guest)(nil) - // AddWCOWBlockCIMs adds WCOW block CIM mounts in the guest. func (gm *Guest) AddWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error { request := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/guestmanager/combinedlayers_lcow.go b/internal/vm/guestmanager/combinedlayers_lcow.go index 2a5b56e5a9..ae09116f54 100644 --- a/internal/vm/guestmanager/combinedlayers_lcow.go +++ b/internal/vm/guestmanager/combinedlayers_lcow.go @@ -11,16 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// LCOWLayersManager exposes combined layer operations in the LCOW guest. -type LCOWLayersManager interface { - // AddLCOWCombinedLayers adds combined layers to the LCOW guest. - AddLCOWCombinedLayers(ctx context.Context, settings guestresource.LCOWCombinedLayers) error - // RemoveLCOWCombinedLayers removes combined layers from the LCOW guest. - RemoveLCOWCombinedLayers(ctx context.Context, settings guestresource.LCOWCombinedLayers) error -} - -var _ LCOWLayersManager = (*Guest)(nil) - // AddLCOWCombinedLayers adds LCOW combined layers in the guest. func (gm *Guest) AddLCOWCombinedLayers(ctx context.Context, settings guestresource.LCOWCombinedLayers) error { modifyRequest := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/guestmanager/combinedlayers_wcow.go b/internal/vm/guestmanager/combinedlayers_wcow.go index 77b63d18f6..330232719d 100644 --- a/internal/vm/guestmanager/combinedlayers_wcow.go +++ b/internal/vm/guestmanager/combinedlayers_wcow.go @@ -11,20 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// WCOWLayersManager exposes combined layer operations in the WCOW guest. -type WCOWLayersManager interface { - // AddWCOWCombinedLayers adds combined layers to the WCOW guest. - AddWCOWCombinedLayers(ctx context.Context, settings guestresource.WCOWCombinedLayers) error - // AddCWCOWCombinedLayers adds combined layers to the CWCOW guest. - AddCWCOWCombinedLayers(ctx context.Context, settings guestresource.CWCOWCombinedLayers) error - // RemoveWCOWCombinedLayers removes combined layers from the WCOW guest. - RemoveWCOWCombinedLayers(ctx context.Context, settings guestresource.WCOWCombinedLayers) error - // RemoveCWCOWCombinedLayers removes combined layers from the CWCOW guest. - RemoveCWCOWCombinedLayers(ctx context.Context, settings guestresource.CWCOWCombinedLayers) error -} - -var _ WCOWLayersManager = (*Guest)(nil) - // AddWCOWCombinedLayers adds WCOW combined layers in the guest. func (gm *Guest) AddWCOWCombinedLayers(ctx context.Context, settings guestresource.WCOWCombinedLayers) error { modifyRequest := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/guestmanager/guest.go b/internal/vm/guestmanager/guest.go index 7f9b705376..b8ef0f11be 100644 --- a/internal/vm/guestmanager/guest.go +++ b/internal/vm/guestmanager/guest.go @@ -17,6 +17,19 @@ import ( "github.com/sirupsen/logrus" ) +// uvm exposes the subset of [vmmanager.UtilityVM] functionality that the +// guest manager needs. +type uvm interface { + // ID returns the user-visible identifier for the Utility VM. + ID() string + // RuntimeID returns the Hyper-V VM GUID. + RuntimeID() guid.GUID + // Wait blocks until the VM exits or ctx is cancelled. + Wait(ctx context.Context) error + // ExitError returns the error that caused the VM to exit, if any. + ExitError() error +} + // Guest manages the GCS connection and guest-side operations for a utility VM. type Guest struct { // mu serializes all operations that interact with the guest connection (gc). @@ -28,22 +41,15 @@ type Guest struct { log *logrus.Entry // uvm is the utility VM that this GuestManager is managing. - // We restrict access to just lifetime manager and VMSocket manager. - // Other APIs are outside the purview of this package. - uvm interface { - vmmanager.LifetimeManager - vmmanager.VMSocketManager - } + // We restrict access to just the methods actually needed by this package. + uvm uvm // gc is the active GCS connection to the guest. // It will be nil if no connection is active. gc *gcs.GuestConnection } // New creates a new Guest Manager. -func New(ctx context.Context, uvm interface { - vmmanager.LifetimeManager - vmmanager.VMSocketManager -}) *Guest { +func New(ctx context.Context, uvm uvm) *Guest { return &Guest{ log: log.G(ctx).WithField(logfields.UVMID, uvm.ID()), uvm: uvm, diff --git a/internal/vm/guestmanager/hvsocket.go b/internal/vm/guestmanager/hvsocket.go index 1108ec1cd6..8cb616fd0f 100644 --- a/internal/vm/guestmanager/hvsocket.go +++ b/internal/vm/guestmanager/hvsocket.go @@ -11,13 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// HVSocketManager exposes the hvSocket operations in the Guest. -type HVSocketManager interface { - UpdateHvSocketAddress(ctx context.Context, settings *hcsschema.HvSocketAddress) error -} - -var _ HVSocketManager = (*Guest)(nil) - // UpdateHvSocketAddress updates the Hyper-V socket address settings for the VM. // These address settings are applied by the GCS every time the VM starts or restores. func (gm *Guest) UpdateHvSocketAddress(ctx context.Context, settings *hcsschema.HvSocketAddress) error { diff --git a/internal/vm/guestmanager/manager.go b/internal/vm/guestmanager/manager.go index 2eeb705bbd..474e9ca63c 100644 --- a/internal/vm/guestmanager/manager.go +++ b/internal/vm/guestmanager/manager.go @@ -8,33 +8,8 @@ import ( "github.com/Microsoft/hcsshim/internal/cmd" "github.com/Microsoft/hcsshim/internal/gcs" - - "github.com/Microsoft/go-winio/pkg/guid" ) -// Manager provides access to guest operations over the GCS connection. -// Call CreateConnection before invoking other methods. -type Manager interface { - // CreateConnection accepts the GCS connection and performs initial setup. - CreateConnection(ctx context.Context, GCSServiceID guid.GUID, opts ...ConfigOption) error - // CloseConnection closes the GCS connection and listener. - CloseConnection() error - // Capabilities returns the guest's declared capabilities. - Capabilities() gcs.GuestDefinedCapabilities - // CreateContainer creates a container within guest using ID `cid` and `config`. - // Once the container is created, it can be managed using the returned `gcs.Container` interface. - // `gcs.Container` uses the underlying guest connection to issue commands to the guest. - CreateContainer(ctx context.Context, cid string, config interface{}) (*gcs.Container, error) - // DumpStacks requests a stack dump from the guest and returns it as a string. - DumpStacks(ctx context.Context) (string, error) - // DeleteContainerState removes persisted state for the container identified by `cid` from the guest. - DeleteContainerState(ctx context.Context, cid string) error - // ExecIntoUVM executes commands specified in the requests in the utility VM. - ExecIntoUVM(ctx context.Context, request *cmd.CmdProcessRequest) (int, error) -} - -var _ Manager = (*Guest)(nil) - // Capabilities returns the capabilities of the guest connection. func (gm *Guest) Capabilities() gcs.GuestDefinedCapabilities { gm.mu.RLock() diff --git a/internal/vm/guestmanager/mapped_directory_lcow.go b/internal/vm/guestmanager/mapped_directory_lcow.go index 6cd9409b47..75d355ffd6 100644 --- a/internal/vm/guestmanager/mapped_directory_lcow.go +++ b/internal/vm/guestmanager/mapped_directory_lcow.go @@ -11,16 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// LCOWDirectoryManager exposes mapped directory operations in the LCOW guest. -type LCOWDirectoryManager interface { - // AddLCOWMappedDirectory maps a directory into the LCOW guest. - AddLCOWMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) error - // RemoveLCOWMappedDirectory unmaps a directory from the LCOW guest. - RemoveLCOWMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) error -} - -var _ LCOWDirectoryManager = (*Guest)(nil) - // AddLCOWMappedDirectory maps a directory into LCOW guest. func (gm *Guest) AddLCOWMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) error { request := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/guestmanager/mapped_directory_wcow.go b/internal/vm/guestmanager/mapped_directory_wcow.go index 8c5ee22700..78a93eb9d1 100644 --- a/internal/vm/guestmanager/mapped_directory_wcow.go +++ b/internal/vm/guestmanager/mapped_directory_wcow.go @@ -11,14 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// WCOWDirectoryManager exposes mapped directory operations in the WCOW guest. -type WCOWDirectoryManager interface { - // AddMappedDirectory maps a directory into the WCOW guest. - AddMappedDirectory(ctx context.Context, settings *hcsschema.MappedDirectory) error -} - -var _ WCOWDirectoryManager = (*Guest)(nil) - // AddMappedDirectory maps a directory into the guest. func (gm *Guest) AddMappedDirectory(ctx context.Context, settings *hcsschema.MappedDirectory) error { request := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/guestmanager/security_policy.go b/internal/vm/guestmanager/security_policy.go index c91a4950b6..bb04749ced 100644 --- a/internal/vm/guestmanager/security_policy.go +++ b/internal/vm/guestmanager/security_policy.go @@ -11,16 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) -// SecurityPolicyManager exposes guest security policy operations. -type SecurityPolicyManager interface { - // AddSecurityPolicy adds a security policy to the guest. - AddSecurityPolicy(ctx context.Context, settings guestresource.ConfidentialOptions) error - // InjectPolicyFragment injects a policy fragment into the guest. - InjectPolicyFragment(ctx context.Context, settings guestresource.SecurityPolicyFragment) error -} - -var _ SecurityPolicyManager = (*Guest)(nil) - // AddSecurityPolicy adds a security policy to the guest. func (gm *Guest) AddSecurityPolicy(ctx context.Context, settings guestresource.ConfidentialOptions) error { request := &hcsschema.ModifySettingRequest{ diff --git a/internal/vm/vmmanager/lifetime.go b/internal/vm/vmmanager/lifetime.go index b2cc737b53..c98cba82a4 100644 --- a/internal/vm/vmmanager/lifetime.go +++ b/internal/vm/vmmanager/lifetime.go @@ -11,57 +11,6 @@ import ( hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" ) -type LifetimeManager interface { - // ID will return a string identifier for the Utility VM. - ID() string - - // RuntimeID will return the Hyper-V VM GUID for the Utility VM. - // - // Only valid after the utility VM has been created. - RuntimeID() guid.GUID - - // Start will power on the Utility VM and put it into a running state. This will boot the guest OS and start all of the - // devices configured on the machine. - Start(ctx context.Context) error - - // Terminate will forcefully power off the Utility VM. - // It waits for the UVM to exit and returns any encountered errors. - Terminate(ctx context.Context) error - - // Close terminates and releases resources associated with the utility VM. - Close(ctx context.Context) error - - // Pause will place the Utility VM into a paused state. The guest OS will be halted and any devices will have be in a - // a suspended state. Save can be used to snapshot the current state of the virtual machine, and Resume can be used to - // place the virtual machine back into a running state. - Pause(ctx context.Context) error - - // Resume will put a previously paused Utility VM back into a running state. The guest OS will resume operation from the point - // in time it was paused and all devices should be un-suspended. - Resume(ctx context.Context) error - - // Save will snapshot the state of the Utility VM at the point in time when the VM was paused. - Save(ctx context.Context, options hcsschema.SaveOptions) error - - // Wait synchronously waits for the Utility VM to terminate. - Wait(ctx context.Context) error - - // PropertiesV2 returns the properties of the Utility VM. - PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error) - - // StartedTime returns the time when the Utility VM entered the running state. - StartedTime() time.Time - - // StoppedTime returns the time when the Utility VM entered the stopped state. - StoppedTime() time.Time - - // ExitError will return any error if the Utility VM exited unexpectedly, or if the Utility VM experienced an - // error after Wait returned, it will return the wait error. - ExitError() error -} - -var _ LifetimeManager = (*UtilityVM)(nil) - // ID returns the ID of the utility VM. func (uvm *UtilityVM) ID() string { return uvm.id diff --git a/internal/vm/vmmanager/pipe.go b/internal/vm/vmmanager/pipe.go index 6de649e5b2..f46904ae14 100644 --- a/internal/vm/vmmanager/pipe.go +++ b/internal/vm/vmmanager/pipe.go @@ -11,17 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" ) -// PipeManager manages adding and removing named pipes for a Utility VM. -type PipeManager interface { - // AddPipe adds a named pipe to the Utility VM. - AddPipe(ctx context.Context, hostPath string) error - - // RemovePipe removes a named pipe from the Utility VM. - RemovePipe(ctx context.Context, hostPath string) error -} - -var _ PipeManager = (*UtilityVM)(nil) - func (uvm *UtilityVM) AddPipe(ctx context.Context, hostPath string) error { modification := &hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeAdd, diff --git a/internal/vm/vmmanager/resources.go b/internal/vm/vmmanager/resources.go index 48f23a5fe1..c91ae3db07 100644 --- a/internal/vm/vmmanager/resources.go +++ b/internal/vm/vmmanager/resources.go @@ -10,23 +10,6 @@ import ( hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" ) -type ResourceManager interface { - // SetCPUGroup assigns the Utility VM to a cpu group. - SetCPUGroup(ctx context.Context, settings *hcsschema.CpuGroup) error - - // UpdateCPULimits updates the CPU limits for the Utility VM. - // `limit` is the percentage of CPU cycles that the Utility VM is allowed to use. - // `weight` is the relative weight of the Utility VM compared to other VMs when CPU cycles are contended. - // `reservation` is the percentage of CPU cycles that are reserved for the Utility VM. - // `maximumFrequencyMHz` is the maximum frequency in MHz that the Utility VM can use. - UpdateCPULimits(ctx context.Context, settings *hcsschema.ProcessorLimits) error - - // UpdateMemory makes a call to the VM's orchestrator to update the VM's size in MB - UpdateMemory(ctx context.Context, memory uint64) error -} - -var _ ResourceManager = (*UtilityVM)(nil) - func (uvm *UtilityVM) SetCPUGroup(ctx context.Context, settings *hcsschema.CpuGroup) error { modification := &hcsschema.ModifySettingRequest{ ResourcePath: resourcepaths.CPUGroupResourcePath, diff --git a/internal/vm/vmmanager/utils.go b/internal/vm/vmmanager/utils.go index ad247352c8..5790fd5b0d 100644 --- a/internal/vm/vmmanager/utils.go +++ b/internal/vm/vmmanager/utils.go @@ -7,9 +7,18 @@ import ( "net" ) +// vmWaiter exposes the subset of VM lifecycle needed by [AcceptConnection]: +// Implemented by [UtilityVM]. +type vmWaiter interface { + // Wait blocks until the VM exits or ctx is cancelled. + Wait(ctx context.Context) error + // ExitError returns the error that caused the VM to exit, if any. + ExitError() error +} + // AcceptConnection accepts a connection and then closes a listener. // It monitors ctx.Done() and uvm.Wait() for early termination. -func AcceptConnection(ctx context.Context, uvm LifetimeManager, l net.Listener, closeConnection bool) (net.Conn, error) { +func AcceptConnection(ctx context.Context, uvm vmWaiter, l net.Listener, closeConnection bool) (net.Conn, error) { // Channel to capture the accept result type acceptResult struct { conn net.Conn diff --git a/internal/vm/vmmanager/vmsocket.go b/internal/vm/vmmanager/vmsocket.go index 637850e931..c6fb7a0bfe 100644 --- a/internal/vm/vmmanager/vmsocket.go +++ b/internal/vm/vmmanager/vmsocket.go @@ -11,18 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" ) -// VMSocketManager manages configuration for a hypervisor socket transport. This includes sockets such as -// HvSocket and Vsock. -type VMSocketManager interface { - // UpdateHvSocketService will update the configuration for the HvSocket service with the specified `serviceID`. - UpdateHvSocketService(ctx context.Context, serviceID string, config *hcsschema.HvSocketServiceConfig) error - - // RemoveHvSocketService will remove the HvSocket service with the specified `serviceID` from the Utility VM. - RemoveHvSocketService(ctx context.Context, serviceID string) error -} - -var _ VMSocketManager = (*UtilityVM)(nil) - // 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 diff --git a/internal/vm/vmmanager/vsmb.go b/internal/vm/vmmanager/vsmb.go index af7af6c2d7..b957f257a2 100644 --- a/internal/vm/vmmanager/vsmb.go +++ b/internal/vm/vmmanager/vsmb.go @@ -11,17 +11,6 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" ) -// VSMBManager manages adding virtual smb shares to a Utility VM. -type VSMBManager interface { - // AddVSMB adds a virtual smb share to a running Utility VM. - AddVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error - - // RemoveVSMB removes a virtual smb share from a running Utility VM. - RemoveVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error -} - -var _ VSMBManager = (*UtilityVM)(nil) - func (uvm *UtilityVM) AddVSMB(ctx context.Context, settings hcsschema.VirtualSmbShare) error { modification := &hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeAdd, From 8c49a2c6f9ef436234663d3a1195796774fba88e Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Mon, 6 Apr 2026 16:47:51 +0530 Subject: [PATCH 2/2] [shimV2] refactor vm controller + add functionalities In this commit, we are refactoring the vm controller to bring it to the same standard as rest of the controllers. Additionally, we are adding- - Update functionality - RuntimeID API - APIs to obtain device controllers Signed-off-by: Harsh Rawat --- internal/controller/vm/doc.go | 8 +- internal/controller/vm/interface.go | 88 ---------------- internal/controller/vm/state.go | 2 +- internal/controller/vm/types.go | 52 +++++++++ internal/controller/vm/vm.go | 129 ++++++++++++++++++----- internal/controller/vm/vm_devices.go | 82 ++++++++++++++ internal/controller/vm/vm_lcow.go | 74 ++++++++++++- internal/controller/vm/vm_unsupported.go | 26 +++++ internal/controller/vm/vm_wcow.go | 57 +++++++++- 9 files changed, 390 insertions(+), 128 deletions(-) delete mode 100644 internal/controller/vm/interface.go create mode 100644 internal/controller/vm/types.go create mode 100644 internal/controller/vm/vm_devices.go create mode 100644 internal/controller/vm/vm_unsupported.go diff --git a/internal/controller/vm/doc.go b/internal/controller/vm/doc.go index 304e117157..389b705e04 100644 --- a/internal/controller/vm/doc.go +++ b/internal/controller/vm/doc.go @@ -4,8 +4,8 @@ // // A Utility VM is a lightweight virtual machine used to host Linux (LCOW) or // Windows (WCOW) containers. This package abstracts the VM lifecycle — -// creation, startup, stats collection, and termination — behind the [Controller] -// interface, with [Manager] as the primary implementation. +// creation, startup, stats collection, and termination — with the [Controller] +// as the primary implementation. // // # Lifecycle // @@ -33,7 +33,7 @@ // // State descriptions: // -// - [StateNotCreated]: initial state after [NewController] is called. +// - [StateNotCreated]: initial state after [New] is called. // - [StateCreated]: after [Controller.CreateVM] succeeds; the VM exists but has not started. // - [StateRunning]: after [Controller.StartVM] succeeds; the guest OS is up and the // Guest Compute Service (GCS) connection is established. @@ -51,7 +51,7 @@ // // # Usage // -// ctrl := vm.NewController() +// ctrl := vm.New() // // if err := ctrl.CreateVM(ctx, &vm.CreateOptions{ // ID: "my-uvm", diff --git a/internal/controller/vm/interface.go b/internal/controller/vm/interface.go deleted file mode 100644 index 3629e21e33..0000000000 --- a/internal/controller/vm/interface.go +++ /dev/null @@ -1,88 +0,0 @@ -//go:build windows - -package vm - -import ( - "context" - "time" - - "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" - hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" - "github.com/Microsoft/hcsshim/internal/protocol/guestresource" - "github.com/Microsoft/hcsshim/internal/shimdiag" - "github.com/Microsoft/hcsshim/internal/vm/guestmanager" - - "github.com/Microsoft/go-winio/pkg/guid" -) - -type Controller interface { - // Guest returns the guest manager instance for this VM. - Guest() *guestmanager.Guest - - // State returns the current VM state. - State() State - - // CreateVM creates and initializes a new VM with the specified options. - // This prepares the VM but does not start it. - CreateVM(ctx context.Context, opts *CreateOptions) error - - // StartVM starts the created VM with the specified options. - // This establishes the guest connection, sets up necessary listeners for - // guest-host communication, and transitions the VM to StateRunning. - StartVM(context.Context, *StartOptions) error - - // ExecIntoHost executes a command in the running UVM. - ExecIntoHost(ctx context.Context, request *shimdiag.ExecProcessRequest) (int, error) - - // DumpStacks dumps the GCS stacks associated with the VM. - DumpStacks(ctx context.Context) (string, error) - - // Wait blocks until the VM exits or the context is cancelled. - // It also waits for log output processing to complete. - Wait(ctx context.Context) error - - Stats(ctx context.Context) (*stats.VirtualMachineStatistics, error) - - TerminateVM(context.Context) error - - // StartTime returns the timestamp when the VM was started. - // Returns zero value of time.time, if the VM is not in StateRunning or StateTerminated. - StartTime() time.Time - - // ExitStatus returns information about the stopped VM, including when it - // stopped and any exit error. Returns an error if the VM is not in StateTerminated. - ExitStatus() (*ExitStatus, error) -} - -// CreateOptions contains the configuration needed to create a new VM. -type CreateOptions struct { - // ID specifies the unique identifier for the VM. - ID string - - // HCSDocument specifies the HCS schema document used to create the VM. - HCSDocument *hcsschema.ComputeSystem -} - -// StartOptions contains the configuration needed to start a VM and establish -// the Guest Compute Service (GCS) connection. -type StartOptions struct { - // GCSServiceID specifies the GUID for the GCS vsock service. - GCSServiceID guid.GUID - - // ConfigOptions specifies additional configuration options for the guest config. - ConfigOptions []guestmanager.ConfigOption - - // ConfidentialOptions specifies security policy and confidential computing - // options for the VM. This is optional and only used for confidential VMs. - ConfidentialOptions *guestresource.ConfidentialOptions -} - -// ExitStatus contains information about a stopped VM's final state. -type ExitStatus struct { - // StoppedTime is the timestamp when the VM stopped. - StoppedTime time.Time - - // Err is the error that caused the VM to stop, if any. - // This will be nil if the VM exited cleanly. - Err error -} diff --git a/internal/controller/vm/state.go b/internal/controller/vm/state.go index 6e98eb4ae1..f9aedddc47 100644 --- a/internal/controller/vm/state.go +++ b/internal/controller/vm/state.go @@ -29,7 +29,7 @@ type State int32 const ( // StateNotCreated indicates the VM has not been created yet. - // This is the initial state when a Controller is first instantiated via [NewController]. + // This is the initial state when a Controller is first instantiated via [New]. // Valid transitions: StateNotCreated → StateCreated (via [Controller.CreateVM]) StateNotCreated State = iota diff --git a/internal/controller/vm/types.go b/internal/controller/vm/types.go new file mode 100644 index 0000000000..383afffe9c --- /dev/null +++ b/internal/controller/vm/types.go @@ -0,0 +1,52 @@ +//go:build windows + +package vm + +import ( + "time" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm/guestmanager" + + "github.com/Microsoft/go-winio/pkg/guid" +) + +// CreateOptions contains the configuration needed to create a new VM. +type CreateOptions struct { + // ID specifies the unique identifier for the VM. + ID string + + // HCSDocument specifies the HCS schema document used to create the VM. + HCSDocument *hcsschema.ComputeSystem + + // NoWritableFileShares disallows writable file shares to the UVM. + NoWritableFileShares bool + + // FullyPhysicallyBacked indicates all memory allocations are backed by physical memory. + FullyPhysicallyBacked bool +} + +// StartOptions contains the configuration needed to start a VM and establish +// the Guest Compute Service (GCS) connection. +type StartOptions struct { + // GCSServiceID specifies the GUID for the GCS vsock service. + GCSServiceID guid.GUID + + // ConfigOptions specifies additional configuration options for the guest config. + ConfigOptions []guestmanager.ConfigOption + + // ConfidentialOptions specifies security policy and confidential computing + // options for the VM. This is optional and only used for confidential VMs. + ConfidentialOptions *guestresource.ConfidentialOptions +} + +// ExitStatus contains information about a stopped VM's final state. +type ExitStatus struct { + // StoppedTime is the timestamp when the VM stopped. + StoppedTime time.Time + + // Err is the error that caused the VM to stop, if any. + // This will be nil if the VM exited cleanly. + Err error +} diff --git a/internal/controller/vm/vm.go b/internal/controller/vm/vm.go index e4c8c42736..fb7abffb3e 100644 --- a/internal/controller/vm/vm.go +++ b/internal/controller/vm/vm.go @@ -12,26 +12,32 @@ import ( "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" "github.com/Microsoft/hcsshim/internal/cmd" + "github.com/Microsoft/hcsshim/internal/controller/device/plan9" + "github.com/Microsoft/hcsshim/internal/controller/device/scsi" + "github.com/Microsoft/hcsshim/internal/controller/device/vpci" 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/protocol/guestresource" "github.com/Microsoft/hcsshim/internal/shimdiag" "github.com/Microsoft/hcsshim/internal/timeout" "github.com/Microsoft/hcsshim/internal/vm/guestmanager" "github.com/Microsoft/hcsshim/internal/vm/vmmanager" "github.com/Microsoft/hcsshim/internal/vm/vmutils" iwin "github.com/Microsoft/hcsshim/internal/windows" - "github.com/containerd/errdefs" + "github.com/Microsoft/hcsshim/pkg/annotations" + "github.com/Microsoft/hcsshim/pkg/ctrdtaskapi" "github.com/Microsoft/go-winio/pkg/process" + "github.com/containerd/errdefs" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "golang.org/x/sys/windows" ) -// Manager is the VM controller implementation that manages the lifecycle of a Utility VM +// Controller is the VM controller implementation that manages the lifecycle of a Utility VM // and its associated resources. -type Manager struct { +type Controller struct { vmID string uvm *vmmanager.UtilityVM guest *guestmanager.Guest @@ -40,7 +46,7 @@ type Manager struct { // Access must be guarded by mu. vmState State - // mu guards the concurrent access to the Manager's fields and operations. + // mu guards the concurrent access to the Controller's fields and operations. mu sync.RWMutex // logOutputDone is closed when the GCS log output processing goroutine completes. @@ -55,14 +61,23 @@ type Manager struct { // isPhysicallyBacked indicates whether the VM is using physical backing for its memory. isPhysicallyBacked bool -} -// Ensure both the Controller, and it's subset Handle are implemented by Manager. -var _ Controller = (*Manager)(nil) + // noWritableFileShares indicates whether writable file shares are disabled for this VM. + noWritableFileShares bool + + // scsiController manages SCSI devices for this VM. + scsiController *scsi.Controller + + // vpciController manages virtual PCI device assignments for this VM. + vpciController *vpci.Controller -// NewController creates a new Manager instance in the [StateNotCreated] state. -func NewController() *Manager { - return &Manager{ + // plan9Controller manages Plan9 file share mounts for this VM. + plan9Controller *plan9.Controller +} + +// New creates a new Controller instance in the [StateNotCreated] state. +func New() *Controller { + return &Controller{ logOutputDone: make(chan struct{}), vmState: StateNotCreated, } @@ -70,20 +85,32 @@ func NewController() *Manager { // Guest returns the guest manager instance for this VM. // The guest manager provides access to guest-host communication. -func (c *Manager) Guest() *guestmanager.Guest { +func (c *Controller) Guest() *guestmanager.Guest { return c.guest } // State returns the current VM state. -func (c *Manager) State() State { +func (c *Controller) State() State { c.mu.RLock() defer c.mu.RUnlock() return c.vmState } +// RuntimeID returns the UVM runtime identifier when the VM is created or running. +func (c *Controller) RuntimeID() string { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.vmState != StateCreated && c.vmState != StateRunning { + return "" + } + + return c.uvm.RuntimeID().String() +} + // CreateVM creates the VM using the HCS document and initializes device state. -func (c *Manager) CreateVM(ctx context.Context, opts *CreateOptions) error { +func (c *Controller) CreateVM(ctx context.Context, opts *CreateOptions) error { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "CreateVM")) c.mu.Lock() @@ -100,17 +127,25 @@ func (c *Manager) CreateVM(ctx context.Context, opts *CreateOptions) error { return fmt.Errorf("failed to create VM: %w", err) } - // Set the Manager parameters after successful creation. + // Set the Controller parameters after successful creation. c.vmID = opts.ID c.uvm = uvm - // Determine if the VM is physically backed based on the HCS document configuration. - // We need this while extracting memory metrics, as some of them are only relevant for physically backed VMs. - c.isPhysicallyBacked = !opts.HCSDocument.VirtualMachine.ComputeTopology.Memory.AllowOvercommit + // Determine if the VM is physically backed based on the create options. + c.isPhysicallyBacked = opts.FullyPhysicallyBacked + // + c.noWritableFileShares = opts.NoWritableFileShares // Initialize the GuestManager for managing guest interactions. // We will create the guest connection via GuestManager during StartVM. c.guest = guestmanager.New(ctx, uvm) + // Eager initialize the SCSI controller as opposed to all other controllers. + // This is because we always use SCSI for attaching scratch VHDs. + c.scsiController, err = newSCSIController(ctx, opts.HCSDocument, c.uvm, c.guest, c.guest) + if err != nil { + return fmt.Errorf("failed to initialize SCSI controller: %w", err) + } + c.vmState = StateCreated return nil } @@ -119,7 +154,7 @@ func (c *Manager) CreateVM(ctx context.Context, opts *CreateOptions) error { // It starts the underlying HCS VM, establishes the GCS connection, // and transitions the VM to [StateRunning]. // On any failure the VM is transitioned to [StateInvalid]. -func (c *Manager) StartVM(ctx context.Context, opts *StartOptions) (err error) { +func (c *Controller) StartVM(ctx context.Context, opts *StartOptions) (err error) { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "StartVM")) c.mu.Lock() @@ -202,9 +237,49 @@ func (c *Manager) StartVM(ctx context.Context, opts *StartOptions) (err error) { return nil } +// Update is used to update the VM configuration on-the-fly. +// It supports modifying resources like CPU and memory while the VM is running. +// It also supports injecting policy fragments or updating the CPU group id for the VM. +func (c *Controller) Update(ctx context.Context, resources interface{}, annots map[string]string) error { + ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "Update")) + + c.mu.Lock() + defer c.mu.Unlock() + + if c.vmState != StateRunning { + return fmt.Errorf("cannot update VM: VM is in state %s", c.vmState) + } + + // If the resource is a policy fragment, inject it directly into the guest and return. + if policyFragment, ok := resources.(*ctrdtaskapi.PolicyFragment); ok { + return c.guest.InjectPolicyFragment(ctx, + guestresource.SecurityPolicyFragment{ + Fragment: policyFragment.Fragment, + }, + ) + } + + // Apply generic VM resource updates (e.g., CPU count, memory). + if err := c.updateVMResources(ctx, resources); err != nil { + return fmt.Errorf("failed to update VM resources: %w", err) + } + + // Update CPU group membership if the corresponding annotation is present. + if cpuGroupID, ok := annots[annotations.CPUGroupID]; ok { + if cpuGroupID == "" { + return errors.New("must specify an ID to use when configuring a VM's cpugroup") + } + if err := c.uvm.SetCPUGroup(ctx, &hcsschema.CpuGroup{Id: cpuGroupID}); err != nil { + return fmt.Errorf("failed to set CPU group: %w", err) + } + } + + return nil +} + // waitForVMExit blocks until the VM exits and then transitions the VM state to [StateTerminated]. // This is called in StartVM in a background goroutine. -func (c *Manager) waitForVMExit(ctx context.Context) { +func (c *Controller) waitForVMExit(ctx context.Context) { // The original context may have timeout or propagate a cancellation // copy the original to prevent it affecting the background wait go routine ctx = context.WithoutCancel(ctx) @@ -216,14 +291,12 @@ func (c *Manager) waitForVMExit(ctx context.Context) { c.mu.Lock() if c.vmState != StateTerminated { c.vmState = StateTerminated - } else { - log.G(ctx).WithField("currentState", c.vmState).Debug("waitForVMExit: state transition to Terminated was a no-op") } c.mu.Unlock() } // ExecIntoHost executes a command in the running UVM. -func (c *Manager) ExecIntoHost(ctx context.Context, request *shimdiag.ExecProcessRequest) (int, error) { +func (c *Controller) ExecIntoHost(ctx context.Context, request *shimdiag.ExecProcessRequest) (int, error) { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "ExecIntoHost")) if request.Terminal && request.Stderr != "" { @@ -256,7 +329,7 @@ func (c *Manager) ExecIntoHost(ctx context.Context, request *shimdiag.ExecProces } // DumpStacks dumps the GCS stacks associated with the VM -func (c *Manager) DumpStacks(ctx context.Context) (string, error) { +func (c *Controller) DumpStacks(ctx context.Context) (string, error) { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "DumpStacks")) // Take read lock at this place. @@ -278,7 +351,7 @@ func (c *Manager) DumpStacks(ctx context.Context) (string, error) { } // Wait blocks until the VM exits and all log output processing has completed. -func (c *Manager) Wait(ctx context.Context) error { +func (c *Controller) Wait(ctx context.Context) error { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "Wait")) // Validate that the VM has been created and can be waited on. @@ -308,7 +381,7 @@ func (c *Manager) Wait(ctx context.Context) error { // Stats returns runtime statistics for the VM including processor runtime and // memory usage. The VM must be in [StateRunning]. -func (c *Manager) Stats(ctx context.Context) (*stats.VirtualMachineStatistics, error) { +func (c *Controller) Stats(ctx context.Context) (*stats.VirtualMachineStatistics, error) { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "Stats")) // Take read lock at this place. @@ -376,7 +449,7 @@ func (c *Manager) Stats(ctx context.Context) (*stats.VirtualMachineStatistics, e // // The context is used for all operations, including waits, so timeouts/cancellations may prevent // proper UVM cleanup. -func (c *Manager) TerminateVM(ctx context.Context) (err error) { +func (c *Controller) TerminateVM(ctx context.Context) (err error) { ctx, _ = log.WithContext(ctx, logrus.WithField(logfields.Operation, "TerminateVM")) c.mu.Lock() @@ -413,7 +486,7 @@ func (c *Manager) TerminateVM(ctx context.Context) (err error) { // StartTime returns the timestamp when the VM was started. // Returns zero value of time.Time if the VM has not yet reached // [StateRunning] or [StateTerminated]. -func (c *Manager) StartTime() (startTime time.Time) { +func (c *Controller) StartTime() (startTime time.Time) { c.mu.RLock() defer c.mu.RUnlock() @@ -427,7 +500,7 @@ func (c *Manager) StartTime() (startTime time.Time) { // ExitStatus returns the final status of the VM once it has reached // [StateTerminated], including the time it stopped and any exit error. // Returns an error if the VM has not yet stopped. -func (c *Manager) ExitStatus() (*ExitStatus, error) { +func (c *Controller) ExitStatus() (*ExitStatus, error) { c.mu.RLock() defer c.mu.RUnlock() diff --git a/internal/controller/vm/vm_devices.go b/internal/controller/vm/vm_devices.go new file mode 100644 index 0000000000..873a5aa161 --- /dev/null +++ b/internal/controller/vm/vm_devices.go @@ -0,0 +1,82 @@ +//go:build windows + +package vm + +import ( + "context" + "fmt" + "strconv" + + "github.com/Microsoft/hcsshim/internal/controller/device/scsi" + "github.com/Microsoft/hcsshim/internal/controller/device/vpci" + "github.com/Microsoft/hcsshim/internal/controller/network" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" +) + +// NetworkController returns a new controller for managing network devices on the VM. +// Since we have a namespace per pod, we create a new controller per call. +func (c *Controller) NetworkController() *network.Controller { + return network.New(c.uvm, c.guest, c.guest) +} + +// SCSIController returns the singleton SCSI device controller for this VM. +func (c *Controller) SCSIController() *scsi.Controller { + return c.scsiController +} + +// VPCIController returns the singleton vPCI device controller for this VM. +func (c *Controller) VPCIController() *vpci.Controller { + c.mu.Lock() + defer c.mu.Unlock() + + if c.vpciController == nil { + c.vpciController = vpci.New(c.uvm, c.guest) + } + + return c.vpciController +} + +// newSCSIController creates a [scsi.Controller] from the HCS document, +// pre-reserving every rootfs slot that already has an attachment in the document. +func newSCSIController( + ctx context.Context, + doc *hcsschema.ComputeSystem, + vm scsi.VMSCSIOps, + linuxGuest scsi.LinuxGuestSCSIOps, + windowsGuest scsi.WindowsGuestSCSIOps, +) (*scsi.Controller, error) { + // If there are no SCSI device controllers in the document, error out. + if doc.VirtualMachine == nil || + doc.VirtualMachine.Devices == nil || + len(doc.VirtualMachine.Devices.Scsi) == 0 { + return nil, fmt.Errorf("expected the VM to have at least one SCSI controller") + } + + // Create a VM SCSI controller. + scsiMap := doc.VirtualMachine.Devices.Scsi + ctrl := scsi.New(len(scsiMap), vm, linuxGuest, windowsGuest) + + // Iterate over the well-known controller GUIDs so the slice index gives us + // the correct controller number directly. + for ctrlIdx, guid := range guestrequest.ScsiControllerGuids { + c, ok := scsiMap[guid] + if !ok { + continue + } + + // Found the controller GUID in the document. + for lunStr := range c.Attachments { + lun, err := strconv.ParseUint(lunStr, 10, 32) + if err != nil { + continue + } + + if err := ctrl.ReserveForRootfs(ctx, uint(ctrlIdx), uint(lun)); err != nil { + return nil, fmt.Errorf("reserve SCSI slot (controller=%d, lun=%d): %w", ctrlIdx, lun, err) + } + } + } + + return ctrl, nil +} diff --git a/internal/controller/vm/vm_lcow.go b/internal/controller/vm/vm_lcow.go index e8c5b51194..52769cb6fb 100644 --- a/internal/controller/vm/vm_lcow.go +++ b/internal/controller/vm/vm_lcow.go @@ -1,4 +1,4 @@ -//go:build windows && !wcow +//go:build windows && lcow package vm @@ -8,19 +8,38 @@ import ( "fmt" "io" + "github.com/Microsoft/hcsshim/internal/controller/device/plan9" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/vm/vmmanager" "github.com/Microsoft/hcsshim/internal/vm/vmutils" + "github.com/Microsoft/hcsshim/osversion" "github.com/Microsoft/go-winio" + "github.com/containerd/errdefs" + "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sync/errgroup" ) +// Plan9Controller returns the singleton controller which can be used +// to manage the Plan9 shares on the Linux UVM. +func (c *Controller) Plan9Controller() *plan9.Controller { + c.mu.Lock() + defer c.mu.Unlock() + + if c.plan9Controller == nil { + c.plan9Controller = plan9.New(c.uvm, c.guest, c.noWritableFileShares) + } + + return c.plan9Controller +} + // setupEntropyListener sets up entropy for LCOW UVMs. // // Linux VMs require entropy to initialize their random number generators during boot. // This method listens on a predefined vsock port and provides cryptographically secure // random data to the Linux init process when it connects. -func (c *Manager) setupEntropyListener(ctx context.Context, group *errgroup.Group) { +func (c *Controller) setupEntropyListener(ctx context.Context, group *errgroup.Group) { group.Go(func() error { // The Linux guest will connect to this port during init to receive entropy. entropyConn, err := winio.ListenHvsock(&winio.HvsockAddr{ @@ -58,7 +77,7 @@ func (c *Manager) setupEntropyListener(ctx context.Context, group *errgroup.Grou // This method establishes a vsock connection to receive log output from GCS // running inside the Linux VM. The logs are parsed and // forwarded to the host's logging system for monitoring and debugging. -func (c *Manager) setupLoggingListener(ctx context.Context, group *errgroup.Group) { +func (c *Controller) setupLoggingListener(ctx context.Context, group *errgroup.Group) { group.Go(func() error { // The GCS will connect to this port to stream log output. logConn, err := winio.ListenHvsock(&winio.HvsockAddr{ @@ -93,6 +112,53 @@ func (c *Manager) setupLoggingListener(ctx context.Context, group *errgroup.Grou // finalizeGCSConnection finalizes the GCS connection for LCOW VMs. // For LCOW, no additional finalization is needed. -func (c *Manager) finalizeGCSConnection(_ context.Context) error { +func (c *Controller) finalizeGCSConnection(_ context.Context) error { + return nil +} + +// updateVMResources applies Linux VM memory and CPU limits from OCI resources. +func (c *Controller) updateVMResources(ctx context.Context, data interface{}) error { + resources, ok := data.(*specs.LinuxResources) + if !ok { + return fmt.Errorf("invalid resource type %T, expected *specs.LinuxResources", data) + } + + if resources.Memory != nil { + if resources.Memory.Limit == nil { + return fmt.Errorf("invalid linux memory limit") + } + + sizeInBytes := uint64(*resources.Memory.Limit) + // Make a call to the VM's orchestrator to update the VM's size in MB + // Internally, HCS will get the number of pages this corresponds to and attempt to assign + // pages to numa nodes evenly + requestedSizeInMB := sizeInBytes / memory.MiB + actual := vmutils.NormalizeMemorySize(ctx, c.vmID, requestedSizeInMB) + + if err := c.uvm.UpdateMemory(ctx, actual); err != nil { + return fmt.Errorf("update vm memory: %w", err) + } + } + + // Translate OCI CPU knobs to HCS processor limits. + if resources.CPU != nil { + processorLimits := &hcsschema.ProcessorLimits{} + if resources.CPU.Quota != nil { + processorLimits.Limit = uint64(*resources.CPU.Quota) + } + if resources.CPU.Shares != nil { + processorLimits.Weight = uint64(*resources.CPU.Shares) + } + + // Support for updating CPU limits was not added until 20H2 build + if osversion.Get().Build < osversion.V20H2 { + return errdefs.ErrNotImplemented + } + + if err := c.uvm.UpdateCPULimits(ctx, processorLimits); err != nil { + return fmt.Errorf("update vm cpu limits: %w", err) + } + } + return nil } diff --git a/internal/controller/vm/vm_unsupported.go b/internal/controller/vm/vm_unsupported.go new file mode 100644 index 0000000000..957c428f1f --- /dev/null +++ b/internal/controller/vm/vm_unsupported.go @@ -0,0 +1,26 @@ +//go:build windows && !wcow && !lcow + +package vm + +import ( + "context" + "fmt" + + "golang.org/x/sync/errgroup" +) + +// setupEntropyListener is a no-op for unsupported guests. +func (c *Controller) setupEntropyListener(_ context.Context, _ *errgroup.Group) {} + +// setupLoggingListener is a no-op for unsupported guests. +func (c *Controller) setupLoggingListener(_ context.Context, _ *errgroup.Group) {} + +// finalizeGCSConnection is not supported for unsupported guests. +func (c *Controller) finalizeGCSConnection(_ context.Context) error { + return fmt.Errorf("unsupported guest") +} + +// updateVMResources is not supported for unsupported guests. +func (c *Controller) updateVMResources(_ context.Context, _ interface{}) error { + return fmt.Errorf("unsupported guest") +} diff --git a/internal/controller/vm/vm_wcow.go b/internal/controller/vm/vm_wcow.go index de6053be8e..50061b3b58 100644 --- a/internal/controller/vm/vm_wcow.go +++ b/internal/controller/vm/vm_wcow.go @@ -10,9 +10,13 @@ import ( "github.com/Microsoft/go-winio" "github.com/Microsoft/hcsshim/internal/gcs/prot" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/vm/vmmanager" "github.com/Microsoft/hcsshim/internal/vm/vmutils" + "github.com/Microsoft/hcsshim/osversion" + "github.com/containerd/errdefs" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "golang.org/x/net/netutil" "golang.org/x/sync/errgroup" @@ -25,7 +29,7 @@ import ( // This is a no-op implementation to satisfy the platform-specific interface. // // For comparison, LCOW VMs require entropy to be provided during boot. -func (c *Manager) setupEntropyListener(_ context.Context, _ *errgroup.Group) {} +func (c *Controller) setupEntropyListener(_ context.Context, _ *errgroup.Group) {} // setupLoggingListener sets up logging for WCOW UVMs. // @@ -37,7 +41,7 @@ func (c *Manager) setupEntropyListener(_ context.Context, _ *errgroup.Group) {} // The listener is configured to accept only one concurrent connection at a time // to prevent resource exhaustion, but will accept new connections if the current one is closed. // This supports scenarios where the logging service inside the VM needs to restart. -func (c *Manager) setupLoggingListener(ctx context.Context, _ *errgroup.Group) { +func (c *Controller) setupLoggingListener(ctx context.Context, _ *errgroup.Group) { // For Windows, the listener can receive a connection later (after VM starts), // so we start the output handler in a goroutine with a non-timeout context. // This allows the output handler to run independently of the VM creation lifecycle. @@ -96,7 +100,7 @@ func (c *Manager) setupLoggingListener(ctx context.Context, _ *errgroup.Group) { // finalizeGCSConnection finalizes the GCS connection for WCOW UVMs. // This is called after CreateConnection succeeds and before the VM is considered fully started. -func (c *Manager) finalizeGCSConnection(ctx context.Context) error { +func (c *Controller) finalizeGCSConnection(ctx context.Context) error { // Prepare the HvSocket address configuration for the external GCS connection. // The LocalAddress is the VM's runtime ID, and the ParentAddress is the // predefined host ID for Windows GCS communication. @@ -114,3 +118,50 @@ func (c *Manager) finalizeGCSConnection(ctx context.Context) error { return nil } + +// updateVMResources applies Windows VM memory and CPU limits from OCI resources. +func (c *Controller) updateVMResources(ctx context.Context, data interface{}) error { + resources, ok := data.(*specs.WindowsResources) + if !ok { + return fmt.Errorf("invalid resource type %T, expected *specs.WindowsResources", data) + } + + if resources.Memory != nil { + if resources.Memory.Limit == nil { + return fmt.Errorf("invalid windows memory limit: nil") + } + + sizeInBytes := *resources.Memory.Limit + // Make a call to the VM's orchestrator to update the VM's size in MB + // Internally, HCS will get the number of pages this corresponds to and attempt to assign + // pages to numa nodes evenly + requestedSizeInMB := sizeInBytes / memory.MiB + actual := vmutils.NormalizeMemorySize(ctx, c.vmID, requestedSizeInMB) + + if err := c.uvm.UpdateMemory(ctx, actual); err != nil { + return fmt.Errorf("update vm memory: %w", err) + } + } + + // Translate OCI CPU knobs to HCS processor limits. + if resources.CPU != nil { + processorLimits := &hcsschema.ProcessorLimits{} + if resources.CPU.Maximum != nil { + processorLimits.Limit = uint64(*resources.CPU.Maximum) + } + if resources.CPU.Shares != nil { + processorLimits.Weight = uint64(*resources.CPU.Shares) + } + + // Support for updating CPU limits was not added until 20H2 build + if osversion.Get().Build < osversion.V20H2 { + return errdefs.ErrNotImplemented + } + + if err := c.uvm.UpdateCPULimits(ctx, processorLimits); err != nil { + return fmt.Errorf("update vm cpu limits: %w", err) + } + } + + return nil +}