Add LCOW live migration support across the controller stacks#2790
Add LCOW live migration support across the controller stacks#2790rawahars wants to merge 3 commits into
Conversation
3ae59a0 to
e4fc26e
Compare
| type Controller struct { | ||
| // mu serializes all public operations on the Controller. | ||
| mu sync.Mutex | ||
| mu sync.RWMutex |
There was a problem hiding this comment.
You changed no usage of this why did it become rw?
|
|
||
| type Controller struct { | ||
| mu sync.Mutex | ||
| mu sync.RWMutex |
|
|
||
| // nextGuestPort is the GCS IO port-allocator floor restored from a | ||
| // migration snapshot; consumed by [Controller.Resume]. | ||
| nextGuestPort uint32 |
There was a problem hiding this comment.
I thought you didnt want to store a policy at this layer?
There was a problem hiding this comment.
Unfortunately for LM, we need to make this exception. These fields are part of the saved state payload and can only be decoded by VM controller. Now we already decode the saved state during Import but these need to be set on the new bridge which happens in Resume. Alternate would be for migration controller to store the opaque state and then send the state as Resume param where it gets decoded again.
That is just redundancy and hence I stored it in the controller layer during import.
| // hcsDocument is the final HCS document used to create this VM, | ||
| // retained for lazy SCSI controller construction and for shipping to | ||
| // the destination during live migration. | ||
| hcsDocument *hcsschema.ComputeSystem |
There was a problem hiding this comment.
You would have to maintain it over time right? How can it be lazy?
There was a problem hiding this comment.
So storing the hcsDocument serves 2 purposes-
- Lazy initialization of SCSI controller which needs to reserve the rootfs slots
- It is shipped to the destination where it is used to recreate the VM
For the LM capable workloads, when we create the VM on the destination, we override the SCSI map with that from the imported controller and therefore it is consistent.
Apart from the SCSI controller, nothing else needs maintenance over time as those features are not supported. When they are, we would override them in a similar manner.
Implement end-to-end live migration support for LCOW pods so a running guest and its workloads can be moved between hosts. Every controller in the stack — VM, pod, Linux container, process, network, and SCSI/Plan9/VPCI devices — gains a save/import lifecycle plus the destination-side patch and resume plumbing needed to rebind local resources and bring the workload back online. Migration lifecycle: - Source: Save serializes a controller's state into a self-describing protobuf envelope and freezes the controller (StateSourceMigrating) so no mutating ops race the transfer; Resume rolls the freeze back, and a finalize Stop or VM teardown terminates it. - Destination: Import rehydrates a controller into a migrating state, Patch repoints saved resources (layer VHDs, process IO/bundle, network namespace) at the destination host, and Resume binds the live VM, guest, and devices and republishes events so containerd treats the task as locally running. AbortMigrated discards an imported-but-never-resumed controller and emits synthetic exits so Delete can proceed. VM controller: - Add the source/destination migrating states and the full HCS migration lifecycle: InitializeLiveMigrationOnSource, StartLiveMigrationOnSource, StartLiveMigrationTransfer, FinalizeLiveMigration, plus StartWithMigrationOptions on the destination. - Exchange the opaque compatibility blob, retain the final HCS document so the destination can recreate an identical VM, and recover the GCS port/bridge-id allocator floors so reissued ids cannot collide. - Make SCSI initialization lazy (built on first use from the HCS document) and handle never-started/destination teardown paths, including the already-stopped HCS error. Controller-specific changes: - SCSI controller switches to an RWMutex and rejects all ops while migrating; ReserveForRootfs now carries the full disk config. - Process, network, container, and VM state machines document and enforce the new migrating states and transitions. - Pod gains a migrating guard, AbortMigrated fan-out, and routes new containers through lazy SCSI init. Includes accompanying unit tests for the new save/import/patch/resume paths across all controllers. Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
| return fmt.Errorf("cannot create VM in state %s: imported HCS document has no VirtualMachine", c.vmState) | ||
| } | ||
| hcsDocument = c.hcsDocument | ||
| hcsDocument.VirtualMachine.MigrationOptions = opts.MigrationOptions |
There was a problem hiding this comment.
Can opts.MigrationOptions ever be nil? If so we might dereference a nil MigrationOptions two lines below. Maybe add a precondition check (return an error if opts.MigrationOptions == nil in StateMigratingImported)
| // SCSI controller is the source of truth for the destination | ||
| // topology (rootfs + hot-added, path-patched); use it verbatim. | ||
| if c.scsiController != nil { | ||
| hcsDocument.VirtualMachine.Devices.Scsi = c.scsiController.HCSAttachments() |
There was a problem hiding this comment.
This code block mutates c.hcsDocument in place (hcsDocument is the same pointer). Since this document is the "source of truth" retained for lazy SCSI init and re-shipped on save, mutating MigrationOptions/Devices.Scsi on the shared instance could bite on any retry/re-entry. Consider deep-copying before stamping migration-specific fields
| func (c *Controller) Resume(ctx context.Context, vm VMSCSIOps, guest GuestSCSIOps) { | ||
| c.mu.Lock() | ||
| defer c.mu.Unlock() | ||
|
|
||
| c.vm = vm | ||
| c.guest = guest | ||
| c.isMigrating = false | ||
|
|
||
| log.G(ctx).Debug("resumed scsi controller") |
There was a problem hiding this comment.
Resume unconditionally sets c.vm, c.guest, and clears isMigrating. Every other controller's Resume guards on the migrating state and errors otherwise. A stray Resume here would silently overwrite the live vm/guest. Worth a guard for consistency/safety (even if callers are currently correct).
| defer c.mu.RUnlock() | ||
|
|
||
| // Collect the config of each indexed disk. | ||
| configs := make([]disk.Config, 0, len(c.disksByPath)) |
There was a problem hiding this comment.
It seems ReserveForRootfs populates controllerSlots but not disksByPath, while Disks() iterates disksByPath.
I think on the destination, vm.Patch calls scsiController.Disks() to GrantVmAccess and since Import does populate disksByPath from the saved host paths, it likely works for the migration path — but the asymmetry seems a little fragile.
Let's confirm rootfs disks are always represented in disksByPath post-import (a test asserting Disks() includes the rootfs would lock this down).
| type Controller struct { | ||
| // mu serializes all public operations on the Controller. | ||
| mu sync.Mutex | ||
| mu sync.RWMutex |
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
…ume and Patch methods Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
|
|
||
| // Save captures the migrating VM's state into a serialized snapshot that the | ||
| // destination host consumes to recreate an equivalent VM. | ||
| func (c *Controller) Save(ctx context.Context) (*anypb.Any, error) { |
There was a problem hiding this comment.
Really nice that every controller here comes with save/import tests. One gap I noticed: the vm controller's migration surface here (Save/Import/Patch/Resume) and vm_migration.go don't seem to have a corresponding _test.go, while pod/network/process/linuxcontainer/scsi all do.
These are the destination side steps that run right before and after the CreateVM branch in vm.go (the one with the nil MigrationOptions case), so a vm-layer test would exercise that whole sequence. Would it be worth adding one? might have missed them if they're coming in the follow-up PR.
There was a problem hiding this comment.
Implement end-to-end live migration support for LCOW pods so a running guest and its workloads can be moved between hosts. Every controller in the stack — VM, pod, Linux container, process, network, and SCSI/Plan9/VPCI devices — gains a save/import lifecycle plus the destination-side patch and resume plumbing needed to rebind local resources and bring the workload back online.
Migration lifecycle:
VM controller:
Controller-specific changes:
Includes accompanying unit tests for the new save/import/patch/resume paths across all controllers.