Skip to content

[live-migration] Preserve host-side GCS logging across migration#2826

Merged
rawahars merged 5 commits into
microsoft:mainfrom
rawahars:vsockexec_relay
Jul 27, 2026
Merged

[live-migration] Preserve host-side GCS logging across migration#2826
rawahars merged 5 commits into
microsoft:mainfrom
rawahars:vsockexec_relay

Conversation

@rawahars

Copy link
Copy Markdown
Contributor

Today a live-migratable LCOW pod gives up host-side GCS logging entirely, because the guest→host log connection is host-local state that migration doesn't carry: after a move there's no listener to reconnect to, and a guest that depended on the log socket would stall on boot. This PR makes that log path survive migration instead of disabling it.

It does so in two steps:

  • Make the forwarder resilient — vsockexec gets an opt-in reconnect mode (-r) that runs as a relay between the child and the host: it tolerates the host listener being absent at boot, and re-dials and resumes if the connection drops. Default behavior is unchanged.

  • Use it on the migration path — live-migratable pods now run vsockexec -r, and the destination host arms a fresh log listener before resume, so the migrated guest simply reconnects and keeps streaming GCS logs.


Net result: GCS logs keep flowing before, during, and after a live migration, with no change for non-migratable pods.

@rawahars
rawahars requested a review from a team as a code owner July 14, 2026 21:49
@rawahars
rawahars requested review from helsaawy and marma-dev July 15, 2026 10:05
Comment thread internal/builder/vm/lcow/kernel_args.go Outdated
rawahars added 3 commits July 24, 2026 00:25
By default vsockexec dials the host once, hands the socket to the child
as its stdio, and execs into it. If the host listener is absent when the
guest starts, or the connection later drops, the child's writes fail and
it exits. Because the child runs under PID 1 in the UVM, its death makes
PID 1 exit and the guest kernel panics, taking the VM down.

Add a new -r flag that keeps vsockexec running as a relay between the
child and the host instead of exec'ing into it:

  - the child writes to a pipe (which never breaks), not to the socket;
  - vsockexec keeps the host connection itself and forwards the pipe to it;
  - it waits and retries if the host is not listening yet (late connect);
  - it re-dials and resends if the connection drops (reconnect);
  - the child's exit status is propagated, so PID 1 still sees the real
    exit code and shutdown semantics are unchanged.

Without -r the behavior is byte-for-byte identical to before, so no
existing caller is affected. stdout and stderr sharing one port continue
to share a single host connection.

Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
Wire the reconnect mode (-r) added in the previous commit into the LCOW
live-migration path so live-migratable pods keep host-side GCS logging
instead of forgoing it.

Before, these pods dropped GCS log forwarding entirely: a plain
vsockexec would block on its outbound connect and stall guest init when
the host log listener was absent, so the wrapper and listener were
skipped. Reconnect mode removes that constraint:

  - buildGCSCommand emits /bin/vsockexec -r for live-migratable
    sandboxes, so the guest tolerates a missing listener and re-dials
    LinuxLogVsockPort after the connection drops;
  - StartWithMigrationOptions arms a fresh listener on the destination
    before start, so the resumed guest can reconnect and resume
    streaming;
  - setupLoggingListener no longer early-returns and Resume no longer
    force-closes logOutputDone.

Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
@rawahars

Copy link
Copy Markdown
Contributor Author

No functional changes since last review. Rebased onto mainline to resolve conflicts.

Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>

@marma-dev marma-dev left a comment

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. I need a bit of clarity on the destination dial-timing, and a minor nit on string construction.

Optional: Would be great to add a Go unit test for the new gate (VM whose cmdline lacks -r → InitializeLiveMigrationOnSource rejects) and, if feasible, an integration test that drops/re-arms the host listener mid-stream to exercise the reconnect path.

Comment thread internal/controller/vm/vm_migration.go Outdated
Comment on lines +135 to +165
@@ -127,6 +157,12 @@ func (c *Controller) StartWithMigrationOptions(ctx context.Context, config *hcs.

// Watch for VM exit in the background.
go c.waitForVMExit(ctx)

// Collect any errors from establishing the log connection.
if err := g.Wait(); err != nil {
return 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.

Does the destination guest dial LinuxLogVsockPort during StartWithMigrationOptions, or only at resume?

setupLoggingListener dispatches the accept onto g, and that goroutine blocks in [AcceptConnection] until a connection arrives or the VM exits. So g.Wait() here can't return until the guest dials. If the destination guest only executes at resume (precopy), this blocks until VM exit and hangs the migration

Since Listen errors are already returned synchronously by setupLoggingListener, g.Wait() only ever waits on the accept, which we don't want to block on. I would suggest dropping the g.Wait() (and the defer g.Wait() and letting the accept run in the background — it closes logOutputDone on VM exit. If I'm wrong about the destination dial timing, please confirm and disregard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice suggestion. I agree that this can likely block the migration to succeed especially since the VM is still inert at the point when this API is called and therefore, the connection will not be accepted which will also lead to destination not moving to StartMigrationTransfer phase.

The VM comes back online only during the Finalize Resume call on HCS which calls into VMWP. Given that our vsockexec relay support buffering, we can remove the log listener setup from the StartWithMigrationOptions to vmController.Resume at which point the VM would have already started.

One another thing is that when the VM resumes and GCS reconnects, it would write logs to log listener socket. Therefore, we will never stall.

Comment thread internal/controller/vm/save_lcow.go Outdated
Comment on lines +234 to +245
g, gctx := errgroup.WithContext(ctx)
defer func() {
_ = g.Wait()
}()

if err := c.setupLoggingListener(gctx, g); err != nil {
return fmt.Errorf("re-arm logging listener on resume: %w", err)
}

// Collect any errors from establishing the log connection.
if err := g.Wait(); err != nil {
return 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.

Same pattern here, and this one can stall even when the guest is alive: the reconnect relay only re-dials on its next write to the dead socket. If GCS is briefly idle, no write → no reconnect → the accept never completes → g.Wait() blocks until GCS next logs (or VM exit).

This also contradicts the adjacent comment ("the accept runs in the background … so a slow guest re-dial cannot stall resume"). I would recommend dropping g.Wait() and relying on the background accept.

@rawahars rawahars Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Log listener setup will not stall since when GCS reconnects, it would write logs to the socket which in turn will trigger the write. If GCS is not connecting or any other error case, we want to explicitly wait so that caller can cancel the context after its timeout.

For clarity, I have removed the comment which mentions that accept runs in background.

Comment thread internal/controller/vm/vm_migration.go Outdated
Comment on lines +48 to +50
if !strings.Contains(kernelCmdLine, fmt.Sprintf("/bin/vsockexec -r -e %d", vmutils.LinuxLogVsockPort)) {
return fmt.Errorf("cannot initialize live migration on source: VM was not created with live-migration support enabled")
}

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.

Two things on the LM-eligibility check:

  • It matches fmt.Sprintf("/bin/vsockexec -r -e %d", …) as a substring, but the builder constructs that string independently in buildGCSCommand. Any formatting drift (flag order/spacing) silently breaks the gate. Could we extract a shared helper/const referenced by both the builder and here so they can't desync?
  • c.hcsDocument.VirtualMachine.Chipset is dereferenced with a nil check on Chipset but not on VirtualMachine (or hcsDocument). Please add the guard for parity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes sure. Implemented the same.

Copilot AI 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

This PR aims to preserve host-side GCS log forwarding for live-migratable LCOW pods by making the guest-side vsock log forwarder resilient to missing/dropped host listeners (via a new vsockexec -r reconnect/relay mode) and ensuring the destination host re-arms the log listener before resuming a migrated VM.

Changes:

  • Add -r reconnect/relay mode to vsockexec to tolerate missing/dropped host listeners without surfacing disconnects to the child process.
  • Re-enable/arm the host-side LCOW GCS log listener on migration destination start and on source rollback resume.
  • Remove LiveMigrationSupportEnabled from SandboxOptions and instead wire reconnect-mode vsockexec directly when building the GCS command line; update tests accordingly.

Reviewed changes

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

Show a summary per file
File Description
vsockexec/vsockexec.c Adds reconnect/relay mode (-r) for resilient stdio forwarding over vsock.
internal/controller/vm/vm_migration.go Ensures destination arms a log listener before start; gates LM init on reconnect-mode kernel cmdline.
internal/controller/vm/vm_lcow.go Removes LM “skip logs” behavior; adjusts log listener completion signaling to be robust to re-arming.
internal/controller/vm/save_lcow.go Re-arms log listener on source rollback resume after blackout.
internal/builder/vm/lcow/specs.go Removes LM flag threading into kernel args parsing/build path.
internal/builder/vm/lcow/specs_test.go Updates LM wiring tests to expect reconnect-mode wrapper when LM is enabled.
internal/builder/vm/lcow/sandbox_options.go Removes LiveMigrationSupportEnabled from SandboxOptions.
internal/builder/vm/lcow/kernel_args.go Always wraps GCS with vsockexec; uses -r when LM annotation is enabled.

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

Comment thread vsockexec/vsockexec.c
Comment thread vsockexec/vsockexec.c
Comment thread internal/controller/vm/vm_migration.go Outdated
Comment thread internal/controller/vm/save_lcow.go Outdated
Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>

@marma-dev marma-dev left a comment

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

@rawahars
rawahars merged commit 8f8c92e into microsoft:main Jul 27, 2026
33 of 34 checks passed
@rawahars
rawahars deleted the vsockexec_relay branch July 27, 2026 06:26
Sign up for free to 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.

5 participants