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