Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions internal/vm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# VM Package

This directory defines the utility VM (UVM) contracts and separates responsibilities into three layers. The goal is to keep
configuration, host-side management, and guest-side actions distinct so each layer can evolve independently.

1. **Builder**: constructs an HCS compute system configuration used to create a VM.
2. **VM Manager**: manages host-side VM configuration and lifecycle (NICs, SCSI, VPMem, etc.).
3. **Guest Manager**: intended for guest-side actions (for example, mounting a disk).

**Note that** this layer does not store UVM host or guest side state. That will be part of the orchestration layer above it.

## Packages and Responsibilities

- `internal/vm`
- Shared types used across layers (for example, `GuestOS`, `SCSIDisk`).
- `internal/vm/builder`
- Interface definitions for shaping the VM configuration (`Builder` interface).
- Concrete implementation of `Builder` for building `hcsschema.ComputeSystem` documents.
- Provides a fluent API for configuring all aspects of the VM document.
- Presently, this package is tightly coupled with HCS backend.
- `internal/vm/vmmanager`
- Interface definitions for UVM lifecycle and host-side management.
- Concrete implementation of `UVM` for running and managing a UVM instance.
- Presently, this package is tightly coupled with HCS backend and only runs HCS backed UVMs.
- Owns lifecycle calls (start/terminate/close) and host-side modifications (NICs, SCSI, VPMem, pipes, VSMB, Plan9).
- Allows creation of the UVM using `vmmanager.Create` which takes a `Builder` and produces a running UVM.
- `internal/vm/guestmanager`
- Reserved for guest-level actions such as mounting disks or performing in-guest configuration.
- Currently empty and intended to grow as guest actions are formalized.

## Typical Flow

1. Build the config using the builder interfaces.
2. Create the VM using the VM manager.
3. Use manager interfaces for lifecycle and host-side changes.
4. Use guest manager interfaces for in-guest actions (when available).

## Example (High Level)

```
builder, _ := builder.New("owner", vm.Linux)

// Configure the VM document.
builder.Memory().SetMemoryLimit(1024)
builder.Processor().SetProcessorConfig(&vm.ProcessorConfig{Count: 2})
// ... other builder configuration

// Create and start the VM.
uvm, _ := vmmanager.Create(ctx, "uvm-id", builder)
_ = uvm.LifetimeManager().Start(ctx)

// Apply host-side updates.
_ = uvm.NetworkManager().AddNIC(ctx, nicID, endpointID, macAddr)
```

## Layer Boundaries (Quick Reference)

- **Builder**: static, pre-create configuration only. No host mutations.
- **VM Manager**: host-side changes and lifecycle operations on an existing UVM.
- **Guest Manager**: guest-side actions, scoped to work that requires in-guest context.

74 changes: 0 additions & 74 deletions internal/vm/builder.go

This file was deleted.

19 changes: 16 additions & 3 deletions internal/vm/hcs/boot.go → internal/vm/builder/boot.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
//go:build windows

package hcs
package builder

import (
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
"github.com/Microsoft/hcsshim/osversion"
"github.com/pkg/errors"
)

func (uvmb *utilityVMBuilder) SetUEFIBoot(dir string, path string, args string) error {
// 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)
// SetLinuxKernelDirectBoot sets Linux direct boot configurations for booting a Utility VM.
SetLinuxKernelDirectBoot(kernel string, initRD string, cmd string) error
}

var _ BootManager = (*utilityVMBuilder)(nil)

func (uvmb *utilityVMBuilder) Boot() 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,
Expand All @@ -17,7 +31,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 {
Expand Down
50 changes: 50 additions & 0 deletions internal/vm/builder/boot_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
68 changes: 68 additions & 0 deletions internal/vm/builder/builder.go
Original file line number Diff line number Diff line change
@@ -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 _ Builder = (*utilityVMBuilder)(nil)

type utilityVMBuilder struct {
guestOS vm.GuestOS
doc *hcsschema.ComputeSystem
assignedDevices map[VPCIDeviceID]*vPCIDevice
}

func New(owner string, guestOS vm.GuestOS) (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, errUnknownGuestOS
}

return &utilityVMBuilder{
guestOS: guestOS,
doc: doc,
assignedDevices: make(map[VPCIDeviceID]*vPCIDevice),
}, nil
}

func (uvmb *utilityVMBuilder) GuestOS() vm.GuestOS {
return uvmb.guestOS
}

func (uvmb *utilityVMBuilder) Get() any {
return uvmb.doc
}
48 changes: 48 additions & 0 deletions internal/vm/builder/builder_test.go
Original file line number Diff line number Diff line change
@@ -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) (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, errUnknownGuestOS) {
t.Fatalf("New() error = %v, want %v", err, errUnknownGuestOS)
}
}

func TestNewBuilder_DefaultDevices(t *testing.T) {
_, cs := newBuilder(t, vm.Windows)
if cs.VirtualMachine.Devices.VirtualSmb == nil {
t.Fatal("VirtualSmb should be initialized for Windows")
}
if cs.VirtualMachine.Devices.Plan9 != nil {
t.Fatal("Plan9 should be nil for Windows")
}

_, cs = newBuilder(t, vm.Linux)
if cs.VirtualMachine.Devices.Plan9 == nil {
t.Fatal("Plan9 should be initialized for Linux")
}
if cs.VirtualMachine.Devices.VirtualSmb != nil {
t.Fatal("VirtualSmb should be nil for Linux")
}
}
Loading