From 3807b376ca174b3dd97b2989ac084a3a9a85a602 Mon Sep 17 00:00:00 2001 From: Harsh Rawat Date: Wed, 11 Feb 2026 04:05:49 +0530 Subject: [PATCH] implement guest manager interfaces for VM operations Signed-off-by: Harsh Rawat --- internal/vm/README.md | 62 +++++++++ internal/vm/guest.go | 107 ++++++++++++++++ internal/vm/guestmanager/block_cims.go | 54 ++++++++ internal/vm/guestmanager/combinedlayers.go | 87 +++++++++++++ internal/vm/guestmanager/device.go | 71 +++++++++++ internal/vm/guestmanager/hcs.go | 114 +++++++++++++++++ internal/vm/guestmanager/hvsocket.go | 29 +++++ internal/vm/guestmanager/manager.go | 52 ++++++++ internal/vm/guestmanager/mapped_directory.go | 69 ++++++++++ internal/vm/guestmanager/network.go | 127 +++++++++++++++++++ internal/vm/guestmanager/request_helpers.go | 20 +++ internal/vm/guestmanager/scsi.go | 86 +++++++++++++ internal/vm/guestmanager/security_policy.go | 52 ++++++++ internal/vm/utils.go | 58 +++++++++ 14 files changed, 988 insertions(+) create mode 100644 internal/vm/README.md create mode 100644 internal/vm/guest.go create mode 100644 internal/vm/guestmanager/block_cims.go create mode 100644 internal/vm/guestmanager/combinedlayers.go create mode 100644 internal/vm/guestmanager/device.go create mode 100644 internal/vm/guestmanager/hcs.go create mode 100644 internal/vm/guestmanager/hvsocket.go create mode 100644 internal/vm/guestmanager/manager.go create mode 100644 internal/vm/guestmanager/mapped_directory.go create mode 100644 internal/vm/guestmanager/network.go create mode 100644 internal/vm/guestmanager/request_helpers.go create mode 100644 internal/vm/guestmanager/scsi.go create mode 100644 internal/vm/guestmanager/security_policy.go create mode 100644 internal/vm/utils.go diff --git a/internal/vm/README.md b/internal/vm/README.md new file mode 100644 index 0000000000..c037a579b3 --- /dev/null +++ b/internal/vm/README.md @@ -0,0 +1,62 @@ +# 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. + - `GuestOps` defines guest-side operations executed via the GCS connection. +- `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` + - Guest-side actions executed via the GCS connection. + - Implements `vm.GuestOps` which provides APIs for network, storage, devices, security policy, and layers management within guest. + - Translates guest operations into GCS modify requests. + +## 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. + +## 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 (GCS-backed). diff --git a/internal/vm/guest.go b/internal/vm/guest.go new file mode 100644 index 0000000000..9895a35596 --- /dev/null +++ b/internal/vm/guest.go @@ -0,0 +1,107 @@ +package vm + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/cow" + "github.com/Microsoft/hcsshim/internal/gcs" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" +) + +// ConfigOption defines a function that modifies the GCS connection config. +type ConfigOption func(*gcs.GuestConnectionConfig) error + +// GuestOps is an interface to interact with +type GuestOps interface { + // CreateConnection creates a connection to the guest. + CreateConnection(ctx context.Context, opts ...ConfigOption) error + // CloseConnection closes the connection to the guest. + CloseConnection() error + // Manager returns an interface to manage general operations in the guest. + Manager() Manager + // LayersManager returns an interface to manage combined layers in the guest. + LayersManager() LayersManager + // GuestNetworkManager returns an interface to manage networking in the guest. + GuestNetworkManager() GuestNetworkManager + // DirectoryManager returns an interface to manage mapped directories in the guest. + DirectoryManager() DirectoryManager + // SecurityPolicyManager returns an interface to manage guest security policy operations. + SecurityPolicyManager() SecurityPolicyManager + // GuestDeviceManager returns an interface to manage device operations in the guest. + GuestDeviceManager() GuestDeviceManager + // BlockCIMsManager returns an interface to manage WCOW block CIMs in the guest. + BlockCIMsManager() BlockCIMsManager + // ScsiManager returns an interface to manage SCSI-related operations in the guest. + ScsiManager() ScsiManager +} + +type Manager interface { + // 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) + // CreateProcess creates a process in the guest. + // Once the process is created, it can be managed using the returned `cow.Process` interface. + // `cow.Process` uses the underlying guest connection to issue commands to the guest. + CreateProcess(ctx context.Context, settings interface{}) (cow.Process, 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 +} + +// LayersManager exposes combined layer operations in the guest. +type LayersManager interface { + AddCombinedLayers(ctx context.Context, settings interface{}) error + AddCWCOWCombinedLayers(ctx context.Context, settings interface{}) error + RemoveCombinedLayers(ctx context.Context, settings interface{}) error + RemoveCWCOWCombinedLayers(ctx context.Context, settings interface{}) error +} + +// GuestNetworkManager exposes guest network operations. +type GuestNetworkManager interface { + AddNetworkNamespace(ctx context.Context, settings interface{}) error + RemoveNetworkNamespace(ctx context.Context, settings interface{}) error + AddNetworkInterface(ctx context.Context, adapterID string, requestType guestrequest.RequestType, settings interface{}) error + AddNetworkInterfaceLCOW(ctx context.Context, settings *guestresource.LCOWNetworkAdapter) error + RemoveNetworkInterface(ctx context.Context, adapterID string, requestType guestrequest.RequestType, settings interface{}) error + RemoveNetworkInterfaceLCOW(ctx context.Context, settings *guestresource.LCOWNetworkAdapter) error +} + +// DirectoryManager exposes mapped directory operations in the guest. +type DirectoryManager interface { + AddMappedDirectory(ctx context.Context, settings *hcsschema.MappedDirectory) error + AddMappedDirectoryLCOW(ctx context.Context, settings guestresource.LCOWMappedDirectory) error + RemoveMappedDirectoryLCOW(ctx context.Context, settings guestresource.LCOWMappedDirectory) error +} + +// SecurityPolicyManager exposes guest security policy operations. +type SecurityPolicyManager interface { + AddSecurityPolicy(ctx context.Context, settings interface{}) error + InjectPolicyFragment(ctx context.Context, settings guestresource.SecurityPolicyFragment) error +} + +// GuestDeviceManager exposes guest device operations. +type GuestDeviceManager interface { + AddVPCIDevice(ctx context.Context, vmBusGUID string) error + AddVPMemDevice(ctx context.Context, settings guestresource.LCOWMappedVPMemDevice) error + RemoveVPMemDevice(ctx context.Context, settings guestresource.LCOWMappedVPMemDevice) error +} + +// BlockCIMsManager exposes guest WCOW block CIM operations. +type BlockCIMsManager interface { + AddWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error + RemoveWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error +} + +// ScsiManager exposes guest SCSI operations. +type ScsiManager interface { + AddMappedVirtualDisk(ctx context.Context, settings interface{}) error + AddMappedVirtualDiskForContainerScratch(ctx context.Context, settings interface{}) error + RemoveMappedVirtualDisk(ctx context.Context, settings interface{}) error + RemoveSCSIDevice(ctx context.Context, settings guestresource.SCSIDevice) error +} diff --git a/internal/vm/guestmanager/block_cims.go b/internal/vm/guestmanager/block_cims.go new file mode 100644 index 0000000000..a1a542c3cc --- /dev/null +++ b/internal/vm/guestmanager/block_cims.go @@ -0,0 +1,54 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +// BlockCIMsManager returns the guest block CIMs manager. +func (gm *guestManager) BlockCIMsManager() vm.BlockCIMsManager { + return gm +} + +// AddWCOWBlockCIMs adds WCOW block CIM mounts in the guest. +func (gm *guestManager) AddWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeWCOWBlockCims, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add WCOW block CIMs") + } + + return nil +} + +// RemoveWCOWBlockCIMs removes WCOW block CIM mounts in the guest. +func (gm *guestManager) RemoveWCOWBlockCIMs(ctx context.Context, settings *guestresource.CWCOWBlockCIMMounts) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeWCOWBlockCims, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove WCOW block CIMs") + } + + return nil +} diff --git a/internal/vm/guestmanager/combinedlayers.go b/internal/vm/guestmanager/combinedlayers.go new file mode 100644 index 0000000000..6666152969 --- /dev/null +++ b/internal/vm/guestmanager/combinedlayers.go @@ -0,0 +1,87 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + + "github.com/pkg/errors" +) + +// LayersManager returns the guest layers manager. +func (gm *guestManager) LayersManager() vm.LayersManager { + return gm +} + +// AddCombinedLayers adds combined layers in the guest. +func (gm *guestManager) AddCombinedLayers(ctx context.Context, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeCombinedLayers, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "AddCombinedLayers failed") + } + return nil +} + +// AddCWCOWCombinedLayers adds combined layers in CWCOW guest. +func (gm *guestManager) AddCWCOWCombinedLayers(ctx context.Context, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeCWCOWCombinedLayers, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "AddCWCOWCombinedLayers failed") + } + return nil +} + +// RemoveCombinedLayers removes combined layers in the guest. +func (gm *guestManager) RemoveCombinedLayers(ctx context.Context, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeCombinedLayers, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "RemoveCombinedLayers failed") + } + return nil +} + +// RemoveCWCOWCombinedLayers removes combined layers in CWCOW guest. +func (gm *guestManager) RemoveCWCOWCombinedLayers(ctx context.Context, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeCWCOWCombinedLayers, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "RemoveCWCOWCombinedLayers failed") + } + return nil +} diff --git a/internal/vm/guestmanager/device.go b/internal/vm/guestmanager/device.go new file mode 100644 index 0000000000..3f2de86df8 --- /dev/null +++ b/internal/vm/guestmanager/device.go @@ -0,0 +1,71 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +// GuestDeviceManager returns the guest device manager. +func (gm *guestManager) GuestDeviceManager() vm.GuestDeviceManager { + return gm +} + +// AddVPCIDevice adds a VPCI device in the guest. +func (gm *guestManager) AddVPCIDevice(ctx context.Context, vmBusGUID string) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeVPCIDevice, + RequestType: guestrequest.RequestTypeAdd, + Settings: guestresource.LCOWMappedVPCIDevice{ + VMBusGUID: vmBusGUID, + }, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add VPCI device") + } + return nil +} + +// AddVPMemDevice adds a VPMem device in the guest. +func (gm *guestManager) AddVPMemDevice(ctx context.Context, settings guestresource.LCOWMappedVPMemDevice) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeVPMemDevice, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add VPMem device") + } + return nil +} + +// RemoveVPMemDevice removes a VPMem device in the guest. +func (gm *guestManager) RemoveVPMemDevice(ctx context.Context, settings guestresource.LCOWMappedVPMemDevice) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeVPMemDevice, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove VPMem device") + } + return nil +} diff --git a/internal/vm/guestmanager/hcs.go b/internal/vm/guestmanager/hcs.go new file mode 100644 index 0000000000..dc497856e0 --- /dev/null +++ b/internal/vm/guestmanager/hcs.go @@ -0,0 +1,114 @@ +//go:build windows + +package guestmanager + +import ( + "context" + "fmt" + "net" + + "github.com/Microsoft/hcsshim/internal/gcs" + "github.com/Microsoft/hcsshim/internal/gcs/prot" + 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/pkg/errors" + "github.com/sirupsen/logrus" +) + +type guestManager struct { + log *logrus.Entry + uvm vm.UVM + + gcListener net.Listener // The listener for the GCS connection + gc *gcs.GuestConnection // The GCS connection +} + +// New creates a new Guest Manager. +func New(ctx context.Context, uvm vm.UVM) (vm.GuestOps, error) { + gm := &guestManager{ + log: log.G(ctx).WithField(logfields.UVMID, uvm.ID()), + uvm: uvm, + } + + conn, err := uvm.VMSocketManager().VMSocketListen(ctx, prot.LinuxGcsVsockPort) + if err != nil { + return nil, errors.Wrap(err, "failed to listen for guest connection") + } + gm.gcListener = conn + + return gm, nil +} + +// WithInitializationState applies initial guest state to the GCS connection config. +func WithInitializationState(state *gcs.InitialGuestState) vm.ConfigOption { + return func(cfg *gcs.GuestConnectionConfig) error { + cfg.InitGuestState = state + return nil + } +} + +// CreateConnection accepts the GCS connection and performs initial setup. +func (gm *guestManager) CreateConnection(ctx context.Context, opts ...vm.ConfigOption) error { + // 1. Accept the connection + conn, err := vm.AcceptConnection(ctx, gm.uvm, gm.gcListener, true) + gm.gcListener = nil // Clear listener as it is now closed/consumed + if err != nil { + return fmt.Errorf("failed to connect to GCS: %w", err) + } + + // 2. Create the default base configuration + gcc := &gcs.GuestConnectionConfig{ + Conn: conn, + Log: gm.log, // Ensure gm has a logger field + IoListen: gcs.HvsockIoListen(gm.uvm.RuntimeID()), + } + + // 3. Apply all passed options. + for _, opt := range opts { + if err := opt(gcc); err != nil { + return fmt.Errorf("failed to apply GCS config option: %w", err) + } + } + + // 4. Start the GCS protocol + gm.gc, err = gcc.Connect(ctx, true) + if err != nil { + return errors.Wrap(err, "failed to connect to GCS") + } + + // 5. Initial setup required for external GCS connection. + hvsocketAddress := &hcsschema.HvSocketAddress{ + LocalAddress: gm.uvm.RuntimeID().String(), + ParentAddress: prot.WindowsGcsHvHostID.String(), + } + + err = gm.updateHvSocketAddress(ctx, hvsocketAddress) + if err != nil { + return errors.Wrap(err, "failed to create GCS connection") + } + + return nil +} + +// Capabilities returns the capabilities of the guest connection. +func (gm *guestManager) Capabilities() gcs.GuestDefinedCapabilities { + return gm.gc.Capabilities() +} + +// CloseConnection closes any active GCS connection and listener. +func (gm *guestManager) CloseConnection() error { + var err error + + if gm.gc != nil { + err = gm.gc.Close() + } + + if gm.gcListener != nil { + err = gm.gcListener.Close() + } + + return err +} diff --git a/internal/vm/guestmanager/hvsocket.go b/internal/vm/guestmanager/hvsocket.go new file mode 100644 index 0000000000..dffd5878cb --- /dev/null +++ b/internal/vm/guestmanager/hvsocket.go @@ -0,0 +1,29 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/pkg/errors" +) + +// updateHvSocketAddress configures the HvSocket address for GCS communication. +func (gm *guestManager) updateHvSocketAddress(ctx context.Context, settings interface{}) error { + conSetupReq := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + RequestType: guestrequest.RequestTypeUpdate, + ResourceType: guestresource.ResourceTypeHvSocket, + Settings: settings, + }, + } + + err := gm.modify(ctx, conSetupReq.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to update hvSocket address") + } + return nil +} diff --git a/internal/vm/guestmanager/manager.go b/internal/vm/guestmanager/manager.go new file mode 100644 index 0000000000..3ff8543948 --- /dev/null +++ b/internal/vm/guestmanager/manager.go @@ -0,0 +1,52 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/cow" + "github.com/Microsoft/hcsshim/internal/gcs" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +func (gm *guestManager) Manager() vm.Manager { + return gm +} + +func (gm *guestManager) CreateContainer(ctx context.Context, cid string, config interface{}) (*gcs.Container, error) { + c, err := gm.gc.CreateContainer(ctx, cid, config) + if err != nil { + return nil, errors.Wrapf(err, "failed to create container %s", cid) + } + + return c, nil +} + +func (gm *guestManager) CreateProcess(ctx context.Context, settings interface{}) (cow.Process, error) { + p, err := gm.gc.CreateProcess(ctx, settings) + if err != nil { + return nil, errors.Wrap(err, "failed to create process") + } + + return p, nil +} + +func (gm *guestManager) DumpStacks(ctx context.Context) (string, error) { + dump, err := gm.gc.DumpStacks(ctx) + if err != nil { + return "", errors.Wrap(err, "failed to dump stacks") + } + + return dump, nil +} + +func (gm *guestManager) DeleteContainerState(ctx context.Context, cid string) error { + err := gm.gc.DeleteContainerState(ctx, cid) + if err != nil { + return errors.Wrapf(err, "failed to delete container state for container %s", cid) + } + + return nil +} diff --git a/internal/vm/guestmanager/mapped_directory.go b/internal/vm/guestmanager/mapped_directory.go new file mode 100644 index 0000000000..cbd759a7ea --- /dev/null +++ b/internal/vm/guestmanager/mapped_directory.go @@ -0,0 +1,69 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +// DirectoryManager returns the guest directory manager. +func (gm *guestManager) DirectoryManager() vm.DirectoryManager { + return gm +} + +// AddMappedDirectory maps a directory into the guest. +func (gm *guestManager) AddMappedDirectory(ctx context.Context, settings *hcsschema.MappedDirectory) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeMappedDirectory, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add mapped directory") + } + return nil +} + +// AddMappedDirectoryLCOW maps a directory into LCOW guest. +func (gm *guestManager) AddMappedDirectoryLCOW(ctx context.Context, settings guestresource.LCOWMappedDirectory) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeMappedDirectory, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add mapped directory for lcow") + } + return nil +} + +// RemoveMappedDirectoryLCOW unmaps a directory from LCOW guest. +func (gm *guestManager) RemoveMappedDirectoryLCOW(ctx context.Context, settings guestresource.LCOWMappedDirectory) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeMappedDirectory, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove mapped directory for lcow") + } + return nil +} diff --git a/internal/vm/guestmanager/network.go b/internal/vm/guestmanager/network.go new file mode 100644 index 0000000000..16fec69d3a --- /dev/null +++ b/internal/vm/guestmanager/network.go @@ -0,0 +1,127 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +// GuestNetworkManager returns the guest network manager. +func (gm *guestManager) GuestNetworkManager() vm.GuestNetworkManager { + return gm +} + +// AddNetworkNamespace adds a network namespace in the guest. +func (gm *guestManager) AddNetworkNamespace(ctx context.Context, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeNetworkNamespace, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add network namespace") + } + return nil +} + +// RemoveNetworkNamespace removes a network namespace in the guest. +func (gm *guestManager) RemoveNetworkNamespace(ctx context.Context, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeNetworkNamespace, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove network namespace") + } + return nil +} + +// AddNetworkInterface adds a network interface using the provided adapter settings. +func (gm *guestManager) AddNetworkInterface(ctx context.Context, adapterID string, requestType guestrequest.RequestType, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeNetwork, + RequestType: guestrequest.RequestTypeAdd, + Settings: guestrequest.NetworkModifyRequest{ + AdapterId: adapterID, + RequestType: requestType, + Settings: settings, // endpoint configuration + }, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add network interface") + } + return nil +} + +// AddNetworkInterfaceLCOW adds a LCOW network interface using a typed adapter. +func (gm *guestManager) AddNetworkInterfaceLCOW(ctx context.Context, settings *guestresource.LCOWNetworkAdapter) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeNetwork, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add network interface for lcow") + } + return nil +} + +// RemoveNetworkInterface removes a network interface using the provided adapter settings. +func (gm *guestManager) RemoveNetworkInterface(ctx context.Context, adapterID string, requestType guestrequest.RequestType, settings interface{}) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + RequestType: guestrequest.RequestTypeRemove, + Settings: guestrequest.NetworkModifyRequest{ + AdapterId: adapterID, + RequestType: requestType, + Settings: settings, // endpoint configuration + }, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove network interface") + } + return nil +} + +// RemoveNetworkInterfaceLCOW removes a LCOW network interface using a typed adapter. +func (gm *guestManager) RemoveNetworkInterfaceLCOW(ctx context.Context, settings *guestresource.LCOWNetworkAdapter) error { + modifyRequest := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeNetwork, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, modifyRequest.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove network interface for lcow") + } + return nil +} diff --git a/internal/vm/guestmanager/request_helpers.go b/internal/vm/guestmanager/request_helpers.go new file mode 100644 index 0000000000..50c4a2e224 --- /dev/null +++ b/internal/vm/guestmanager/request_helpers.go @@ -0,0 +1,20 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + "github.com/pkg/errors" +) + +var errGuestConnectionUnavailable = errors.New("guest connection not initialized") + +// modify sends a guest modification request via the guest connection. +// This is a helper method to avoid having to check for a nil guest connection in every method that needs to send a request. +func (gm *guestManager) modify(ctx context.Context, req interface{}) error { + if gm.gc == nil { + return errGuestConnectionUnavailable + } + return gm.gc.Modify(ctx, req) +} diff --git a/internal/vm/guestmanager/scsi.go b/internal/vm/guestmanager/scsi.go new file mode 100644 index 0000000000..9a03d6502b --- /dev/null +++ b/internal/vm/guestmanager/scsi.go @@ -0,0 +1,86 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +// ScsiManager returns the guest SCSI manager. +func (gm *guestManager) ScsiManager() vm.ScsiManager { + return gm +} + +// AddMappedVirtualDisk attaches a mapped virtual disk in the guest. +func (gm *guestManager) AddMappedVirtualDisk(ctx context.Context, settings interface{}) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeMappedVirtualDisk, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add mapped virtual disk") + } + return nil +} + +// AddMappedVirtualDiskForContainerScratch attaches a scratch disk in the guest. +func (gm *guestManager) AddMappedVirtualDiskForContainerScratch(ctx context.Context, settings interface{}) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeMappedVirtualDiskForContainerScratch, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add mapped virtual disk for container scratch") + } + return nil +} + +// RemoveMappedVirtualDisk detaches a mapped virtual disk in the guest. +func (gm *guestManager) RemoveMappedVirtualDisk(ctx context.Context, settings interface{}) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeMappedVirtualDisk, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove mapped virtual disk") + } + return nil +} + +// RemoveSCSIDevice removes a SCSI device in the guest. +func (gm *guestManager) RemoveSCSIDevice(ctx context.Context, settings guestresource.SCSIDevice) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeSCSIDevice, + RequestType: guestrequest.RequestTypeRemove, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to remove SCSI device") + } + return nil +} diff --git a/internal/vm/guestmanager/security_policy.go b/internal/vm/guestmanager/security_policy.go new file mode 100644 index 0000000000..91bb222d36 --- /dev/null +++ b/internal/vm/guestmanager/security_policy.go @@ -0,0 +1,52 @@ +//go:build windows + +package guestmanager + +import ( + "context" + + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/vm" + "github.com/pkg/errors" +) + +// SecurityPolicyManager returns the guest security policy manager. +func (gm *guestManager) SecurityPolicyManager() vm.SecurityPolicyManager { + return gm +} + +// AddSecurityPolicy adds a security policy to the guest. +func (gm *guestManager) AddSecurityPolicy(ctx context.Context, settings interface{}) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeSecurityPolicy, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to add security policy") + } + return nil +} + +// InjectPolicyFragment injects a policy fragment into the guest. +func (gm *guestManager) InjectPolicyFragment(ctx context.Context, settings guestresource.SecurityPolicyFragment) error { + request := &hcsschema.ModifySettingRequest{ + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypePolicyFragment, + RequestType: guestrequest.RequestTypeAdd, + Settings: settings, + }, + } + + err := gm.modify(ctx, request.GuestRequest) + if err != nil { + return errors.Wrap(err, "failed to inject security policy fragment") + } + return nil +} diff --git a/internal/vm/utils.go b/internal/vm/utils.go new file mode 100644 index 0000000000..2ee9b057bc --- /dev/null +++ b/internal/vm/utils.go @@ -0,0 +1,58 @@ +//go:build windows + +package vm + +import ( + "context" + "net" +) + +// 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 UVM, l net.Listener, closeConnection bool) (net.Conn, error) { + // Channel to capture the accept result + type acceptResult struct { + conn net.Conn + err error + } + resultCh := make(chan acceptResult) + + go func() { + c, err := l.Accept() + resultCh <- acceptResult{c, err} + }() + + // Channel to monitor VM exit + vmExitCh := make(chan error, 1) + go func() { + // Wait blocks until the VM terminates + vmExitCh <- uvm.LifetimeManager().Wait(ctx) + }() + + select { + case res := <-resultCh: + if closeConnection { + _ = l.Close() + } + return res.conn, res.err + case <-ctx.Done(): + return nil, ctx.Err() + case <-vmExitCh: + } + + _ = l.Close() + res := <-resultCh + if res.err == nil { + return res.conn, res.err + } + + // Prefer context error to VM error to accept error in order to return the + // most useful error. + if ctx.Err() != nil { + return nil, ctx.Err() + } + if uvm.LifetimeManager().ExitError() != nil { + return nil, uvm.LifetimeManager().ExitError() + } + return nil, res.err +}