Skip to content

Commit 7ad61e4

Browse files
committed
posts/2025-02-20/01: add "NixOS VM test with network access"
1 parent 5e10346 commit 7ad61e4

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# NixOS VM test with network access
2+
3+
The [first post of this
4+
blog](/posts/2024-07-26/01-writing-nixos-tests-for-fun-and-profit.md)
5+
described how I wrote integration tests for a hobby project of mine to interact
6+
with Hyprland (a Window Manager for Wayland).
7+
8+
This time, I had another project of mine that desperately needed a better way
9+
to run integrations tests:
10+
[nix-alien](https://github.com/thiagokokada/nix-alien). [I already had
11+
integration
12+
tests](https://github.com/thiagokokada/nix-alien/blob/7e687663d2054fa1708284bd42731c6be62b1667/integration-tests.nix)
13+
that I wrote a few years ago, but they're basically just a bunch of glorified
14+
shell scripts wrapped in Nix for (some) sanity.
15+
16+
But this time I had much better Nix knowledge and I knew about NixOS VM tests,
17+
so why not port the old tests to use it instead? Since [GitHub
18+
Actions](https://github.com/thiagokokada/hyprland-go/actions/workflows/nix.yaml)
19+
and
20+
[nix-installer-action](https://github.com/DeterminateSystems/nix-installer-action)
21+
supports KVM, it means I can even run the tests inside GitHub Actions for free
22+
(since it is an open-source project).
23+
24+
Taking the knowledge from my previous blog post this was mostly a breeze, and I
25+
got a bootable `flake.nix` file really fast. But then I hit a road-block: how
26+
can I give the VM access to internet?
27+
28+
You see, NixOS VM tests are not really different from any other Nix derivation,
29+
so they're just as isolated. This is great to ensure reproducibility, but it is
30+
annoying sometimes. In `nix-alien` case, one test tries to run `nix-shell`
31+
inside the VM, and this ends up trying to download a copy of
32+
[nixpkgs](https://github.com/NixOS/nixpkgs) tarball. I tried as much as I could
33+
to preload the tarball directly to the VM's `/nix/store`, but nothing worked
34+
and I didn't want to leave the test in the previous state (that wasn't even
35+
working anymore in GitHub Actions thanks to some recent changes).
36+
37+
So I decided to be pragmatic: it is better to have an impure NixOS VM test than
38+
to keep the current state. And the easiest way to fix was to give access to VM
39+
to the internet. But how to do so?
40+
41+
The answer is actually simple, but it is kind puzzling if you don't know where
42+
to look for. First, you need to add support for internet inside the VM. DHCP is
43+
the easiest option and will be shown in the example below, but you can
44+
configure it any other way (e.g.: static IP):
45+
46+
```nix
47+
{
48+
description = "nix-alien";
49+
50+
inputs = {
51+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
52+
};
53+
54+
outputs =
55+
{ nixpkgs, ... }:
56+
let
57+
system = "x86_64-linux";
58+
pkgs = nixpkgs.legacyPackages.${system};
59+
in
60+
{
61+
checks.${system}.it = pkgs.testers.runNixOSTest {
62+
name = "integration-tests";
63+
64+
nodes.machine =
65+
{ pkgs, lib, ... }:
66+
{
67+
nix.nixPath = [ "${nixpkgs}" ];
68+
69+
# Here is the important bit, starting the DHCP server
70+
networking.useDHCP = true;
71+
72+
virtualisation = {
73+
cores = 2;
74+
memorySize = 2048;
75+
diskSize = 10240;
76+
};
77+
};
78+
79+
testScript = # python
80+
''
81+
start_all()
82+
83+
# Good to make sure that DHCP client started before testing
84+
machine.wait_for_unit("multi-user.target")
85+
86+
machine.succeed("ping -c 3 8.8.8.8")
87+
'';
88+
};
89+
};
90+
}
91+
```
92+
93+
If you put the file above in `flake.nix` and run `nix flake check -L`, the test
94+
will eventually fail with an error similar to this one:
95+
96+
```console
97+
vm-test-run-integration-tests> machine: must succeed: ping -c 3 8.8.8.8
98+
vm-test-run-integration-tests> machine: output: PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
99+
vm-test-run-integration-tests> From 10.0.2.2 icmp_seq=1 Destination Net Unreachable
100+
vm-test-run-integration-tests> From 10.0.2.2 icmp_seq=2 Destination Net Unreachable
101+
vm-test-run-integration-tests> From 10.0.2.2 icmp_seq=3 Destination Net Unreachable
102+
vm-test-run-integration-tests> --- 8.8.8.8 ping statistics ---
103+
vm-test-run-integration-tests> 3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2042ms
104+
vm-test-run-integration-tests> cleanup
105+
vm-test-run-integration-tests> kill machine (pid 11)
106+
vm-test-run-integration-tests> qemu-system-x86_64: terminating on signal 15 from pid 8 (/nix/store/0l539chjmcq5kdd43j6dgdjky4sjl7hl-python3-3.12.8/bin/python3.12)
107+
vm-test-run-integration-tests> kill vlan (pid 9)
108+
vm-test-run-integration-tests> (finished: cleanup, in 0.02 seconds)
109+
vm-test-run-integration-tests> Traceback (most recent call last):
110+
vm-test-run-integration-tests> File "/nix/store/ahpc056hlclhnv4qrdlfb525pk3shnxw-nixos-test-driver-1.1/bin/.nixos-test-driver-wrapped", line 9, in <module>
111+
vm-test-run-integration-tests> sys.exit(main())
112+
vm-test-run-integration-tests> ^^^^^^
113+
vm-test-run-integration-tests> File "/nix/store/ahpc056hlclhnv4qrdlfb525pk3shnxw-nixos-test-driver-1.1/lib/python3.12/site-packages/test_driver/__init__.py", line 146, in main
114+
vm-test-run-integration-tests> driver.run_tests()
115+
vm-test-run-integration-tests> File "/nix/store/ahpc056hlclhnv4qrdlfb525pk3shnxw-nixos-test-driver-1.1/lib/python3.12/site-packages/test_driver/driver.py", line 174, in run_tests
116+
```
117+
118+
What gives? Well, this is the Nix sandbox in action. It is how Nix ensure
119+
reproducibility, but we don't want it in this case. While you can disable it in
120+
the Nix configuration, the easiest way is to simple disable it during the `nix`
121+
call:
122+
123+
```console
124+
$ nix flake check -L --option sandbox false
125+
```
126+
127+
And everything works as except:
128+
129+
```console
130+
vm-test-run-integration-tests> machine: must succeed: ping -c 3 8.8.8.8
131+
vm-test-run-integration-tests> (finished: must succeed: ping -c 3 8.8.8.8, in 2.08 seconds)
132+
vm-test-run-integration-tests> (finished: run the VM test script, in 16.77 seconds)
133+
vm-test-run-integration-tests> test script finished in 16.87s
134+
vm-test-run-integration-tests> cleanup
135+
vm-test-run-integration-tests> kill machine (pid 1470953)
136+
vm-test-run-integration-tests> qemu-system-x86_64: terminating on signal 15 from pid 1470949 (/nix/store/0l539chjmcq5kdd43j6dgdjky4sjl7hl-python3-3.12.8/bin/python3.12)
137+
vm-test-run-integration-tests> kill vlan (pid 1470951)
138+
vm-test-run-integration-tests> (finished: cleanup, in 0.01 seconds)
139+
```

0 commit comments

Comments
 (0)