Skip to content

Add LCOW live migration source and destination APIs - #2803

Merged
Harsh Rawat (rawahars) merged 3 commits into
microsoft:mainfrom
rawahars:lm_service
Jul 27, 2026
Merged

Add LCOW live migration source and destination APIs#2803
Harsh Rawat (rawahars) merged 3 commits into
microsoft:mainfrom
rawahars:lm_service

Conversation

@rawahars

Copy link
Copy Markdown
Contributor

Summary

Introduce an end-to-end live-migration workflow for LCOW sandboxes, letting a running sandbox be handed off from a source shim to a destination shim.

  • Add a migration Controller that sequences a single session through a source or destination lifecycle: the source prepares and exports an opaque sandbox snapshot (VM + per-pod state), while the destination imports it, patches each container onto its new IDs, builds the destination VM, transfers memory over a shared socket, and finalizes with a resume or stop.

  • Expose the migration gRPC/ttrpc surface on the LCOW shim service (PrepareAndExportSandbox, ImportSandbox, PrepareSandbox, TransferSandbox, FinalizeSandbox, Cancel, Cleanup, CreateDuplicateSocket, Notifications) and register it alongside the task and sandbox services.

  • Wire migration into the task lifecycle: route destination-side CreateTask for rehydrated containers into a patch path, and reject task mutations (state, delete, kill) while a session is in progress.

  • Add duplicated transport-socket adoption, a subscriber-based progress notification stream, and proto/HCS conversion helpers for migration options and events.

This PR needs #2790 to be merged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, doc.go / state.go diagrams are excellent and match the code — nice 👍 .
I would like run.Transfer and service-owned maps comments addressed (or explained). Rest are nice to have.

Comment threadinternal/controller/migration/controller.go Outdated
Comment threadinternal/controller/migration/controller_destination.go Outdated
Comment threadinternal/controller/migration/socket.go
// Reattach each migrated pod's containers and processes on this host.
for podID, podCtrl := range c.podControllers {
if err := podCtrl.Resume(ctx, c.vmController, events, true /* isDestination */); err != nil {
return fmt.Errorf("resume pod %q from migration: %w", podID, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the RESUME path, if one podCtrl.Resume fails partway through the loop, Finalize returns before setting StateFinalized, so the session stays in StateTransferCompleted with the VM already resumed and some pods live. Since finalizeSandboxInternal just propagates the error to the caller, what's the intended recovery here, is the caller expected to retry Finalize, or fall back to a STOP?

It seems like a retried RESUME would call vm.Resume again on an already-running VM, which vm.Resume rejects, so I wasn't sure how the partial-failure case is meant to unwind. I could be missing where that's handled upstream.

@rawahars

Copy link
Copy Markdown
ContributorAuthor

Rebased onto the mainline. No code changes from the last review were made.

@rawahars

Copy link
Copy Markdown
ContributorAuthor

Rebased onto the mainline. No code changes from the last review were made.

@rawahars

Copy link
Copy Markdown
ContributorAuthor

Manish Ranjan Mahanta (@marma-dev)Jie Chen (@jiechen0826) All your review suggestions have been incorporated in 166ff74. Please take a look now.

Introduce an end-to-end live-migration workflow for LCOW sandboxes, letting
a running sandbox be handed off from a source shim to a destination shim.
- Add a migration Controller that sequences a single session through a
source or destination lifecycle: the source prepares and exports an
opaque sandbox snapshot (VM + per-pod state), while the destination
imports it, patches each container onto its new IDs, builds the
destination VM, transfers memory over a shared socket, and finalizes
with a resume or stop.
- Expose the migration gRPC/ttrpc surface on the LCOW shim service
(PrepareAndExportSandbox, ImportSandbox, PrepareSandbox, TransferSandbox,
FinalizeSandbox, Cancel, Cleanup, CreateDuplicateSocket, Notifications)
and register it alongside the task and sandbox services.
- Wire migration into the task lifecycle: route destination-side CreateTask
for rehydrated containers into a patch path, and reject task mutations
(state, delete, kill) while a session is in progress.
- Add duplicated transport-socket adoption, a subscriber-based progress
notification stream, and proto/HCS conversion helpers for migration
options and events.
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
Give the live-migration session predictable, caller-facing behavior:
- Cancellation & wind-down: a session can be aborted from any active
state and always finalizes then cleans up back to idle, even after a
failure or a partly-applied finalize.
- Predictable retries: resource snapshots can be retaken and resumes are
idempotent, while duplicate or out-of-order session calls are rejected
instead of silently reapplied.
- Typed errors: calls report invalid-argument, failed-precondition, or
already-exists so callers can branch on them.
Backed by unit tests and a documented state machine.
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
@rawahars

Copy link
Copy Markdown
ContributorAuthor

Rebased onto the mainline

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduce an end-to-end LCOW live migration workflow in the lcow-v2 shim by adding a migration session controller (source + destination lifecycles), wiring it into the shim service surface (ttrpc), and updating underlying controllers to support retry-safe save/import/patch/resume semantics and richer error classification.

Changes:

  • Add an internal/controller/migration controller implementing a session state machine, duplicated-socket adoption, and subscriber-based progress notifications.
  • Extend VM/pod/container/process/network/device controller migration save/import/patch/resume flows (including idempotent retries) and wrap key failures with containerd/errdefs.
  • Register and implement migration APIs in cmd/containerd-shim-lcow-v2/service, including a destination CreateTask patch path for rehydrated containers.

Reviewed changes

Copilot reviewed 45 out of 47 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
pkg/migration/parse.goAdds helpers to map HCS migration events/origin/results into wire notification fields.
pkg/migration/parse_test.goAdds unit tests for migration proto parsing/mapping and blackout-exited details parsing.
internal/vm/vmmanager/migration.goAdds UtilityVM.CancelLiveMigration wrapper.
internal/hcs/v2/migration.goAdds System.CancelLiveMigration entry point (currently stubbed).
internal/controller/vm/vm.goEnsures migrating flag cleared during VM termination to release bridge earlier.
internal/controller/vm/vm_migration.goBroadens finalize state-acceptance and adds VM-level cancel API.
internal/controller/vm/save_lcow.goTightens migration state preconditions and wraps errors with errdefs; adds resume idempotency.
internal/controller/process/save.goMakes Save retry-safe from StateSourceMigrating, adds resume idempotency, wraps errors with errdefs.
internal/controller/process/save_test.goUpdates/extends tests for new Save/Resume semantics and error wrapping.
internal/controller/pod/save_lcow.goMakes Resume idempotent and improves error classification for Import/Patch.
internal/controller/pod/save_lcow_test.goUpdates tests for new Resume/Patch behavior and errdefs wrapping.
internal/controller/network/save.goMakes Save retry-safe from StateSourceMigrating, adds resume/reset idempotency, wraps errors with errdefs.
internal/controller/network/save_test.goUpdates tests for new Save/Resume/Reset behavior and errdefs wrapping.
internal/controller/migration/types.goDefines migration controller interfaces/options used by service + tests.
internal/controller/migration/state.goDefines migration session state machine and stringification.
internal/controller/migration/socket.goImplements duplicated transport socket adoption and validation.
internal/controller/migration/socket_test.goAdds end-to-end tests for socket descriptor adoption and guards.
internal/controller/migration/notifications.goAdds subscriber fan-out + replay notification stream over VM migration events.
internal/controller/migration/notifications_test.goAdds tests for subscription lifecycle, replay, fan-out, and controller Subscribe integration.
internal/controller/migration/mocks/mock_lcow_migration.goAdds gomock for migration controller’s VM interface.
internal/controller/migration/doc.goDocuments the migration controller lifecycle, failure/cancel paths, and notifications.
internal/controller/migration/controller.goImplements core transfer/finalize/cancel/cleanup sequencing for a session.
internal/controller/migration/controller_source.goImplements source-side prepare + export snapshot workflow.
internal/controller/migration/controller_source_test.goAdds source-side unit tests.
internal/controller/migration/controller_destination.goImplements destination import + per-container patch + prepare destination VM.
internal/controller/migration/controller_destination_test.goAdds destination-side unit tests.
internal/controller/linuxcontainer/save.goMakes container save retry-safe, adds resume idempotency, wraps errors with errdefs.
internal/controller/linuxcontainer/save_test.goUpdates tests for new save/resume semantics and error wrapping.
internal/controller/device/vpci/save.goMakes VPCI Save a no-op when empty; wraps unsupported state with errdefs.
internal/controller/device/vpci/save_lcow_test.goUpdates VPCI Save tests to assert errdefs wrapping.
internal/controller/device/scsi/save.goWraps validation/precondition failures with errdefs; tightens import validation comments.
internal/controller/device/scsi/save_test.goUpdates SCSI tests for errdefs wrapping and new invalid-argument cases.
internal/controller/device/scsi/mount/save_lcow.goWraps mount save precondition failures with errdefs.
internal/controller/device/scsi/mount/save_lcow_test.goUpdates mount save tests for errdefs wrapping.
internal/controller/device/scsi/disk/save.goWraps disk save precondition failures with errdefs.
internal/controller/device/scsi/disk/save_test.goUpdates disk save tests for errdefs wrapping.
internal/controller/device/plan9/save.goMakes Plan9 Save a no-op when empty; wraps unsupported state with errdefs.
internal/controller/device/plan9/save_test.goUpdates Plan9 Save tests to assert errdefs wrapping.
cmd/containerd-shim-lcow-v2/service/types.goExtends service vmController interface to satisfy migration controller needs.
cmd/containerd-shim-lcow-v2/service/service.goInstantiates migration controller and registers migration service; adds migration-in-progress guard.
cmd/containerd-shim-lcow-v2/service/service_task_internal.goRoutes migrated CreateTask into patch path and rejects task ops during migration.
cmd/containerd-shim-lcow-v2/service/service_task_internal_test.goUpdates task API tests to reflect migration/VM-running guard changes.
cmd/containerd-shim-lcow-v2/service/service_sandbox_internal_test.goUpdates service test construction to include migration controller.
cmd/containerd-shim-lcow-v2/service/service_migration.goAdds instrumentation-layer migration service methods and registration plumbing.
cmd/containerd-shim-lcow-v2/service/service_migration_internal.goImplements migration service business logic (prepare/export/import/prepare/transfer/finalize/notifications/cancel/cleanup).
cmd/containerd-shim-lcow-v2/service/mocks/mock_service.goUpdates mocks to include migration-related vmController methods.
Files not reviewed (2)
  • cmd/containerd-shim-lcow-v2/service/mocks/mock_service.go: Generated file
  • internal/controller/migration/mocks/mock_lcow_migration.go: Generated file
Comments suppressed due to low confidence (1)

internal/controller/migration/controller.go:486

  • Cleanup() resets c.socketReady to a new channel without closing the old one. If Transfer() previously started and is still blocked waiting on the old socketReady (e.g., canceled/failed before socket registration), that goroutine will keep waiting until timeout, causing a long-lived goroutine leak.
	// Reset all session-scoped state so the controller can host a new session.
c.sessionID, c.sandboxID, c.origin = "", "", ""
c.vmController = nil
c.podControllers = nil
c.containerPodMapping = nil
c.pendingPatches = nil
c.socketReady = make(chan struct{})
c.state = StateIdle

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadinternal/hcs/v2/migration.go
Comment threadinternal/controller/migration/controller.go
Comment threadinternal/controller/migration/controller_source.go
Comment threadcmd/containerd-shim-lcow-v2/service/service_task_internal.go
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
@rawahars
Harsh Rawat (rawahars) merged commit 7670554 into microsoft:mainJul 27, 2026
20 checks passed
@rawahars
Harsh Rawat (rawahars) deleted the lm_service branch July 27, 2026 16:45
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@rawahars@jiechen0826@marma-dev