Uh oh!
There was an error while loading. Please reload this page.
controller/vm: add unit tests for VM Controller state machine - #2634
controller/vm: add unit tests for VM Controller state machine#2634Shreyansh Sancheti (shreyanshjain7174) wants to merge 1 commit into
Conversation
eeccba4 to
687d39eCompare687d39e to
ef82816Comparee8cacd2 to
4718b32Compare4718b32 to
09f8fa8Comparee179fe0 to
aba0d5fCompare3d5fad6 to
fbae1c6Compare35ea6c5 to
a470ed1CompareDefines per-platform vmLifetime and guestManager seam interfaces over *vmmanager.UtilityVM and *guestmanager.Guest. Each interface is a superset of the methods Controller invokes directly plus the device methods consumed by the wired-in sub-controllers (vpci/network/plan9/scsi). *vmmanager and *guestmanager satisfy these via Go structural typing; no concrete sister fields needed. The per-platform split is necessary because: - AddPlan9 / RemovePlan9 are LCOW-only on *vmmanager.UtilityVM. - Guest network signatures differ across LCOW and WCOW. - vPCI guest and Plan9 guest methods are LCOW-only. - UpdateHvSocketAddress is WCOW-only. Guest() uses a type assertion to preserve its concrete return type. 30 tests covering: Idempotency (3): duplicate StartVM no-op, TerminateVM from Terminated and NotCreated. State guards (8): StartVM rejects NotCreated/Terminated/Invalid, ExecIntoHost rejects Terminated, terminal+stderr input validation, Update methods reject non-Running (4 subtests), Stats rejects Terminated, Wait rejects NotCreated, DumpStacks rejects Terminated, UpdateCPUGroup rejects empty ID. TerminateVM cleanup chain (6): full Terminate->CloseConnection->Close ordering, guest close failure swallowed, uvm.Terminate failure swallowed, uvm.Close failure -> StateInvalid, recovery from Invalid, retry failure stays Invalid. Error propagation (4): ExecIntoHost guest error, DumpStacks guest error, UpdatePolicyFragment guest error, ExitStatus not-terminated error. API delegation (4): ExecIntoHost exit code forwarding, DumpStacks with and without capability, ExitStatus value assembly. Wait + background goroutine (5): Wait from Terminated with log drain, Wait context cancelled during log drain, Wait Running happy path, waitForVMExit Running->Terminated transition, waitForVMExit overwrites Invalid->Terminated (pins current behaviour). Mocks generated per-platform (mocks/mock_lcow_vm.go, mock_wcow_vm.go) with matching //go:build constraints. Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
a470ed1 to
1611cc8Compare| "github.com/Microsoft/go-winio/pkg/guid" | ||
| ) | ||
| // vmLifetime and guestManager are defined per-platform in vm_lcow.go and |
There was a problem hiding this comment.
Please remove this comment. The idea identified here is implicit for V2 shims and therefore, this just adds noise.
| // vmLifetime is the LCOW-flavoured seam over [*vmmanager.UtilityVM]. | ||
| // AddPlan9 / RemovePlan9 are LCOW-only on the host side, hence the per-platform split. | ||
| type vmLifetime interface { |
There was a problem hiding this comment.
rename this to utilityVM
| // vmLifetime is the WCOW-flavoured seam over [*vmmanager.UtilityVM]. | ||
| // WCOW has no host-side Plan9, hence the per-platform split. | ||
| type vmLifetime interface { |
There was a problem hiding this comment.
You do not need to declare the interface for each variant.
You can use approach similar to how it's done here-
hcsshim/internal/controller/vm/vm.go
Line 69 in fa5c029
Idea is keep the common APIs in types.go and then platform specific ones in vm_lcow and vm_wcow.
types.go
typeutilityVMinterface {
ID() stringRuntimeID() guid.GUIDStart(ctx context.Context) errorWait(ctx context.Context) errorTerminate(ctx context.Context) errorClose(ctx context.Context) errorSetCPUGroup(ctx context.Context, settings*hcsschema.CpuGroup) errorUpdateCPULimits(ctx context.Context, settings*hcsschema.ProcessorLimits) errorUpdateMemory(ctx context.Context, memoryuint64) errorPropertiesV2(ctx context.Context, types...hcsschema.PropertyType) (*hcsschema.Properties, error)
StartedTime() time.TimeStoppedTime() time.TimeExitError() errorAddDevice(ctx context.Context, vmbusGUID guid.GUID, settings hcsschema.VirtualPciDevice) errorRemoveDevice(ctx context.Context, vmbusGUID guid.GUID) errorAddNIC(ctx context.Context, nicIDstring, settings*hcsschema.NetworkAdapter) errorRemoveNIC(ctx context.Context, nicIDstring, settings*hcsschema.NetworkAdapter) errorAddSCSIDisk(ctx context.Context, disk hcsschema.Attachment, controlleruint, lunuint) errorRemoveSCSIDisk(ctx context.Context, controlleruint, lunuint) error// Platform specific APIsplatformUVM
}
typeguestManagerinterface {
CreateConnection(ctx context.Context, gcsServiceID guid.GUID, opts...guestmanager.ConfigOption) errorCloseConnection() errorAddSecurityPolicy(ctx context.Context, opts guestresource.ConfidentialOptions) errorInjectPolicyFragment(ctx context.Context, fragment guestresource.SecurityPolicyFragment) errorCapabilities() gcs.GuestDefinedCapabilitiesDumpStacks(ctx context.Context) (string, error)
ExecIntoUVM(ctx context.Context, request*cmd.CmdProcessRequest) (int, error)
// Platform specific APIsplatformGuestManager }vm_lcow.go
typeplatformUVMinterface {
// LCOW-specific methodsAddPlan9(ctx context.Context, settings hcsschema.Plan9Share) errorRemovePlan9(ctx context.Context, settings hcsschema.Plan9Share) error
}
typeplatformGuestManagerinterface {
// LCOW-specific methodsAddVPCIDevice(ctx context.Context, settings guestresource.LCOWMappedVPCIDevice) errorAddNetworkInterface(ctx context.Context, settings*guestresource.LCOWNetworkAdapter) errorRemoveNetworkInterface(ctx context.Context, settings*guestresource.LCOWNetworkAdapter) errorAddMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) errorRemoveMappedDirectory(ctx context.Context, settings guestresource.LCOWMappedDirectory) errorAddMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) errorRemoveMappedVirtualDisk(ctx context.Context, settings guestresource.LCOWMappedVirtualDisk) errorRemoveSCSIDevice(ctx context.Context, settings guestresource.SCSIDevice) error
}vm_wcow.go
typeplatformUVMinterface {
}
typeplatformGuestManagerinterface {
// WCOW-specific methodsUpdateHvSocketAddress(ctx context.Context, address*hcsschema.HvSocketAddress) errorAddNetworkInterface(ctx context.Context, adapterIDstring, requestType guestrequest.RequestType, settings*hcn.HostComputeEndpoint) errorRemoveNetworkInterface(ctx context.Context, adapterIDstring, requestType guestrequest.RequestType, settings*hcn.HostComputeEndpoint) errorAddNetworkNamespace(ctx context.Context, settings*hcn.HostComputeNamespace) errorRemoveNetworkNamespace(ctx context.Context, settings*hcn.HostComputeNamespace) errorAddMappedVirtualDisk(ctx context.Context, settings guestresource.WCOWMappedVirtualDisk) errorAddMappedVirtualDiskForContainerScratch(ctx context.Context, settings guestresource.WCOWMappedVirtualDisk) errorRemoveMappedVirtualDisk(ctx context.Context, settings guestresource.WCOWMappedVirtualDisk) error
}There was a problem hiding this comment.
Second thoughts- Please see the uber review comment.
| // guestManager is the LCOW-flavoured seam over [*guestmanager.Guest]. | ||
| type guestManager interface { | ||
| CreateConnection(ctx context.Context, gcsServiceID guid.GUID, opts ...guestmanager.ConfigOption) error |
There was a problem hiding this comment.
Please rebase on current main. There is one more Guest API- PrepareConnection.
There was a problem hiding this comment.
The interface segregation can be simplified. You can possibly cherry-pick rawahars@9239ccf
This refactors the VM controller to use interfaces.
Closing this PR — the interface refactoring has been superseded by rawahars's cherry-pick (rawahars@9239ccf) which is now the base for the tests. Opening a new PR ( New PR addresses all review feedback from this PR:
|
Correction to previous comment: this PR is superseded by #2738 which has the tests rebased on the interface refactoring from the first commit. All review feedback from this PR has been addressed in the new one. |
Depends on #2627 — must merge first.
Adds unit tests for the VM Controller state machine (
internal/controller/vm/). To make the Manager testable, two field types are changed to their existing interfaces (vmmanager.LifetimeManager, combinedGuestManager). Mocks are generated withmockgenfollowing theinternal/windows/mock/pattern.Tests are biased toward production failure paths — resource cleanup, state corruption, and half-started scenarios — rather than guard-check permutations.
30 test cases covering: TerminateVM transitions (idempotency, Created/Running→Terminated, Close fail→Invalid, Invalid recovery, Terminate() error swallowed with Close still called, CloseConnection() error swallowed with Close still called), CreateVM duplicate-call rejection, StartVM failure paths (uvm.Start fails→Invalid, listener/connection failure→Invalid, idempotency when already Running, wrong-state rejection), ExecIntoHost (terminal+stderr precondition, success, exec error), DumpStacks (supported, not supported, dump error), StartTime/ExitStatus for relevant states, representative guard checks (one per method for realistic wrong-state calls), and State.String() for all values. All tests run without admin or HCS.