at master 5.8 kB view raw
1{ pkgs, lib, ... }: 2{ 3 name = "turbovnc-headless-server"; 4 meta = { 5 maintainers = with lib.maintainers; [ nh2 ]; 6 }; 7 8 nodes.machine = 9 { pkgs, ... }: 10 { 11 12 environment.systemPackages = with pkgs; [ 13 mesa-demos 14 procps # for `pkill`, `pidof` in the test 15 scrot # for screenshotting Xorg 16 turbovnc 17 ]; 18 19 programs.turbovnc.ensureHeadlessSoftwareOpenGL = true; 20 21 networking.firewall = { 22 # Reject instead of drop, for failures instead of hangs. 23 rejectPackets = true; 24 allowedTCPPorts = [ 25 5900 # VNC :0, for seeing what's going on in the server 26 ]; 27 }; 28 29 # So that we can ssh into the VM, see e.g. 30 # https://nixos.org/manual/nixos/stable/#sec-nixos-test-port-forwarding 31 services.openssh.enable = true; 32 users.mutableUsers = false; 33 # `test-instrumentation.nix` already sets an empty root password. 34 # The following have to all be set to allow an empty SSH login password. 35 services.openssh.settings.PermitRootLogin = "yes"; 36 services.openssh.settings.PermitEmptyPasswords = "yes"; 37 security.pam.services.sshd.allowNullPassword = true; # the default `UsePam yes` makes this necessary 38 }; 39 40 testScript = '' 41 def wait_until_terminated_or_succeeds( 42 termination_check_shell_command, 43 success_check_shell_command, 44 get_detail_message_fn, 45 retries=60, 46 retry_sleep=0.5, 47 ): 48 def check_success(): 49 command_exit_code, _output = machine.execute(success_check_shell_command) 50 return command_exit_code == 0 51 52 for _ in range(retries): 53 exit_check_exit_code, _output = machine.execute(termination_check_shell_command) 54 is_terminated = exit_check_exit_code != 0 55 if is_terminated: 56 if check_success(): 57 return 58 else: 59 details = get_detail_message_fn() 60 raise Exception( 61 f"termination check ({termination_check_shell_command}) triggered without command succeeding ({success_check_shell_command}); details: {details}" 62 ) 63 else: 64 if check_success(): 65 return 66 import time 67 time.sleep(retry_sleep) 68 69 if not check_success(): 70 details = get_detail_message_fn() 71 raise Exception( 72 f"action timed out ({success_check_shell_command}); details: {details}" 73 ) 74 75 76 # Below we use the pattern: 77 # (cmd | tee stdout.log) 3>&1 1>&2 2>&3 | tee stderr.log 78 # to capture both stderr and stdout while also teeing them, see: 79 # https://unix.stackexchange.com/questions/6430/how-to-redirect-stderr-and-stdout-to-different-files-and-also-display-in-termina/6431#6431 80 81 82 # Starts headless VNC server, backgrounding it. 83 def start_xvnc(): 84 xvnc_command = " ".join( 85 [ 86 "Xvnc", 87 ":0", 88 "-iglx", 89 "-auth /root/.Xauthority", 90 "-geometry 1240x900", 91 "-depth 24", 92 "-rfbwait 5000", 93 "-deferupdate 1", 94 "-verbose", 95 "-securitytypes none", 96 # We don't enforce localhost listening such that we 97 # can connect from outside the VM using 98 # env QEMU_NET_OPTS=hostfwd=tcp::5900-:5900 $(nix-build nixos/tests/turbovnc-headless-server.nix -A driver)/bin/nixos-test-driver 99 # for testing purposes, and so that we can in the future 100 # add another test case that connects the TurboVNC client. 101 # "-localhost", 102 ] 103 ) 104 machine.execute( 105 # Note trailing & for backgrounding. 106 f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr >&2 &", 107 ) 108 109 110 # Waits until the server log message that tells us that GLX is ready 111 # (requires `-verbose` above), avoiding screenshoting racing below. 112 def wait_until_xvnc_glx_ready(): 113 machine.wait_until_succeeds("test -f /tmp/Xvnc.stderr") 114 wait_until_terminated_or_succeeds( 115 termination_check_shell_command="pidof Xvnc", 116 success_check_shell_command="grep 'GLX: Initialized DRISWRAST' /tmp/Xvnc.stderr", 117 get_detail_message_fn=lambda: "Contents of /tmp/Xvnc.stderr:\n" 118 + machine.succeed("cat /tmp/Xvnc.stderr"), 119 ) 120 121 122 # Starts glxgears, backgrounding it. Waits until it prints the `GL_RENDERER`. 123 # Does not quit glxgears. 124 def test_glxgears_prints_renderer(): 125 machine.execute( 126 # Note trailing & for backgrounding. 127 "(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr >&2 &" 128 ) 129 machine.wait_until_succeeds("test -f /tmp/glxgears.stderr") 130 wait_until_terminated_or_succeeds( 131 termination_check_shell_command="pidof glxgears", 132 success_check_shell_command="grep 'GL_RENDERER' /tmp/glxgears.stdout", 133 get_detail_message_fn=lambda: "Contents of /tmp/glxgears.stderr:\n" 134 + machine.succeed("cat /tmp/glxgears.stderr"), 135 ) 136 137 138 with subtest("Start Xvnc"): 139 start_xvnc() 140 wait_until_xvnc_glx_ready() 141 142 with subtest("Run 3D application (glxgears)"): 143 test_glxgears_prints_renderer() 144 145 # Take screenshot; should display the glxgears. 146 machine.succeed("scrot --display :0 /tmp/glxgears.png") 147 148 # Copy files down. 149 machine.copy_from_vm("/tmp/glxgears.png") 150 machine.copy_from_vm("/tmp/glxgears.stdout") 151 machine.copy_from_vm("/tmp/Xvnc.stdout") 152 machine.copy_from_vm("/tmp/Xvnc.stderr") 153 ''; 154 155}