Apparently this wasn't the case before, but now it is
In [3]: machine.shell_interact()
??? Warning (DeprecationWarning): Use the SSH backdoor instead
File "<ipython-input-3-4e72ea6a987e>", line 1
Reviewing #512771 got me thinking and I'm not sure I see a good reason
to keep this method given the SSH backdoor functionality has a much
better UX.
I'd propose we deprecate it, await 26.11 to see if people complain and
remove it alltogether after that.
The nspawn test machine passes a driver-owned AF_UNIX SOCK_DGRAM socket as the
container's NOTIFY_SOCKET (systemd-nspawn --notify-ready=yes). The driver only
drained it while waiting for the initial READY=1 during boot.
A container's PID 1 re-sends READY=1 on every `systemctl daemon-reexec` (the
same Manager.Reexecute that switch-to-configuration issues whenever the systemd
package changed). With nothing draining the socket after boot, its receive
buffer fills and PID 1 blocks in sendmsg() to NOTIFY_SOCKET (wchan
unix_wait_for_peer) while re-executing, so it never finishes re-initializing.
From then on every in-container `systemctl` call hangs or fails with
'Transport endpoint is not connected', which hangs wait_for_unit /
switch-to-configuration for the full timeout. QEMU nodes have no such notify
socket and are unaffected.
Drain the notify socket in a dedicated daemon thread for the container's whole
lifetime so PID 1 never blocks on the re-exec READY=1 send. Readiness and the
leader PID are now read from that thread's state instead of polling the socket
inline during boot.
Assisted-by: Claude:claude-opus-4-8
Before this commit, `wait_for_console_text` would read one line every
900ms before trying again to read the next line.
This commit fixes that behavior by greedily reading all buffer that is
available for match trying to find a match rather than reading one per
retry iteration.
In effect this moves the loop inside the `console_matches` helper and
makes the behavior of `wait_for_console_text` with timeout behave the
same way as with timeout.
This also provides a test that showcases the problem.
TL;DR add `assert` to correct location for type-narrowing
typeshed recently updated their definition of `select` in https://github.com/python/typeshed/commit/bcb6399e155cb6444150fe98dfd9c24eb0343016.
The result is that the members of the lists passed to `select` now
require an upper bound of `FileDescriptorLike`.
The `self.shell` variable is typed as `socket | None`, but previously
passed checking against typeshed due to the missing upper-bound.
Despite the source code including a type-narrowing `assert`, `ty` does
not make use of it within the inner function that `select` is used in,
meaning `self.shell` wasn't narrowed at the appropriate place.
Now that typeshed has added this upper-bound, type-checking starts to fail, as evidenced in
https://github.com/NixOS/nixpkgs/pull/523288#issuecomment-4529738483.
So the correct fix is just to add the `assert` into the inner function.
For backwards-compat reason it's legal to do
runTest {
nodes.foo = { /* ... */ };
testScript = ''
machine.start()
# do your thing
'';
}
This makes several places in the codebase unnecessarily complex and is
something people shouldn't be using anyways. Additionally, I was
reminded by people that this can actually be confusing when you expect
the variable to be named differently.
Hence, deprecate this behavior and kill it in a few releases down the
road.
`vhost-device-vsock`[1] is a custom implementation of AF_VSOCK, but the
application on the host-side uses a UNIX domain-socket. This gives us
the following nice properties:
* We don't need to do `--arg sandbox-paths /dev/vhost-vsock` anymore for
debugging builds within the sandbox. That means, untrusted users can
also debug these kinds of tests now.
* This prevents CID conflicts on the host-side, i.e. there's no need for
using `sshBackdoor.vsockOffset` for tests anymore.
A big shout-out goes to Allison Karlitskaya, the developer of test.thing[2]
who talked about this approach to do AF_VSOCK on All Systems Go 2025.
This patch requires systemd 258[3] because this contains `vhost-mux` in
its SSH config which is needed to connect to the VMs from now on.
To not blow up the patches even more, this only uses AF_VSOCK for the
debugger. A potential follow-up for the future would be a removal of the
current `backdoor.service` and replace it entirely by this
functionality.
The internal implementation tries to be consistent with how VLANs and
machines are handled, i.e. the processes are started when the Driver's
context is entered and cleaned up in __exit__().
I decided to push the process management and creation of sockets for
vhost-device-vsock into its own class, that's an implementation detail
and not a concern for the test-driver. In fact, `vhost-device-vsock` is
something we can drop once QEMU implements native support for using
AF_UNIX on the host-side[4]. `VsockPair` is its own class since
returning e.g. a triple of `(Path, Path, Int)` would be ambiguous in
what is the guest and what the host path (and frankly, I found it hard
to distinguish the two when reading the docs of `vhost-device-vsock`
initially).
Finally, now that we can do the SSH backdoor without adding additional
devices to the sandbox, I figured, it's time to write a test-case for
it.
[1] https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md
[2] https://codeberg.org/lis/test.thing
[3] https://github.com/NixOS/nixpkgs/pull/427968
[4] https://gitlab.com/qemu-project/qemu/-/issues/2095
The context manager's purpose is to allocate its resources in `__enter__` and
release them again in `__exit__`.
Right now, the approach is merely a hack since we allocate everything in
the constructor, but use the context-manager protocol as way to reliably
terminate everything.
This isn't a functional change, but merely a correctness change by using
the methods the way they were intended.