Merge pull request #116282 from nh2/programs-turbovnc
turbovnc: Add programs.turbovnc, add test for headless software OpenGL
This commit is contained in:
commit
296c47d7b2
5 changed files with 230 additions and 0 deletions
|
@ -177,6 +177,7 @@
|
||||||
./programs/tmux.nix
|
./programs/tmux.nix
|
||||||
./programs/traceroute.nix
|
./programs/traceroute.nix
|
||||||
./programs/tsm-client.nix
|
./programs/tsm-client.nix
|
||||||
|
./programs/turbovnc.nix
|
||||||
./programs/udevil.nix
|
./programs/udevil.nix
|
||||||
./programs/usbtop.nix
|
./programs/usbtop.nix
|
||||||
./programs/vim.nix
|
./programs/vim.nix
|
||||||
|
|
54
nixos/modules/programs/turbovnc.nix
Normal file
54
nixos/modules/programs/turbovnc.nix
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# Global configuration for the SSH client.
|
||||||
|
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.programs.turbovnc;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
|
||||||
|
programs.turbovnc = {
|
||||||
|
|
||||||
|
ensureHeadlessSoftwareOpenGL = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to set up NixOS such that TurboVNC's built-in software OpenGL
|
||||||
|
implementation works.
|
||||||
|
|
||||||
|
This will enable <option>hardware.opengl.enable</option> so that OpenGL
|
||||||
|
programs can find Mesa's llvmpipe drivers.
|
||||||
|
|
||||||
|
Setting this option to <code>false</code> does not mean that software
|
||||||
|
OpenGL won't work; it may still work depending on your system
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
This option is also intended to generate warnings if you are using some
|
||||||
|
configuration that's incompatible with using headless software OpenGL
|
||||||
|
in TurboVNC.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.ensureHeadlessSoftwareOpenGL {
|
||||||
|
|
||||||
|
# TurboVNC has builtin support for Mesa llvmpipe's `swrast`
|
||||||
|
# software rendering to implemnt GLX (OpenGL on Xorg).
|
||||||
|
# However, just building TurboVNC with support for that is not enough
|
||||||
|
# (it only takes care of the X server side part of OpenGL);
|
||||||
|
# the indiviudual applications (e.g. `glxgears`) also need to directly load
|
||||||
|
# the OpenGL libs.
|
||||||
|
# Thus, this creates `/run/opengl-driver` populated by Mesa so that the applications
|
||||||
|
# can find the llvmpipe `swrast.so` software rendering DRI lib via `libglvnd`.
|
||||||
|
# This comment exists to explain why `hardware.` is involved,
|
||||||
|
# even though 100% software rendering is used.
|
||||||
|
hardware.opengl.enable = true;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -408,6 +408,7 @@ in
|
||||||
trickster = handleTest ./trickster.nix {};
|
trickster = handleTest ./trickster.nix {};
|
||||||
trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {};
|
trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {};
|
||||||
tuptime = handleTest ./tuptime.nix {};
|
tuptime = handleTest ./tuptime.nix {};
|
||||||
|
turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {};
|
||||||
ucg = handleTest ./ucg.nix {};
|
ucg = handleTest ./ucg.nix {};
|
||||||
udisks2 = handleTest ./udisks2.nix {};
|
udisks2 = handleTest ./udisks2.nix {};
|
||||||
unbound = handleTest ./unbound.nix {};
|
unbound = handleTest ./unbound.nix {};
|
||||||
|
|
171
nixos/tests/turbovnc-headless-server.nix
Normal file
171
nixos/tests/turbovnc-headless-server.nix
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||||
|
name = "turbovnc-headless-server";
|
||||||
|
meta = {
|
||||||
|
maintainers = with lib.maintainers; [ nh2 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
machine = { pkgs, ... }: {
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
glxinfo
|
||||||
|
procps # for `pkill`, `pidof` in the test
|
||||||
|
scrot # for screenshotting Xorg
|
||||||
|
turbovnc
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.turbovnc.ensureHeadlessSoftwareOpenGL = true;
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
# Reject instead of drop, for failures instead of hangs.
|
||||||
|
rejectPackets = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
5900 # VNC :0, for seeing what's going on in the server
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# So that we can ssh into the VM, see e.g.
|
||||||
|
# http://blog.patapon.info/nixos-local-vm/#accessing-the-vm-with-ssh
|
||||||
|
services.openssh.enable = true;
|
||||||
|
services.openssh.permitRootLogin = "yes";
|
||||||
|
users.extraUsers.root.password = "";
|
||||||
|
users.mutableUsers = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
def wait_until_terminated_or_succeeds(
|
||||||
|
termination_check_shell_command,
|
||||||
|
success_check_shell_command,
|
||||||
|
get_detail_message_fn,
|
||||||
|
retries=60,
|
||||||
|
retry_sleep=0.5,
|
||||||
|
):
|
||||||
|
def check_success():
|
||||||
|
command_exit_code, _output = machine.execute(success_check_shell_command)
|
||||||
|
return command_exit_code == 0
|
||||||
|
|
||||||
|
for _ in range(retries):
|
||||||
|
exit_check_exit_code, _output = machine.execute(termination_check_shell_command)
|
||||||
|
is_terminated = exit_check_exit_code != 0
|
||||||
|
if is_terminated:
|
||||||
|
if check_success():
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
details = get_detail_message_fn()
|
||||||
|
raise Exception(
|
||||||
|
f"termination check ({termination_check_shell_command}) triggered without command succeeding ({success_check_shell_command}); details: {details}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if check_success():
|
||||||
|
return
|
||||||
|
time.sleep(retry_sleep)
|
||||||
|
|
||||||
|
if not check_success():
|
||||||
|
details = get_detail_message_fn()
|
||||||
|
raise Exception(
|
||||||
|
f"action timed out ({success_check_shell_command}); details: {details}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Below we use the pattern:
|
||||||
|
# (cmd | tee stdout.log) 3>&1 1>&2 2>&3 | tee stderr.log
|
||||||
|
# to capture both stderr and stdout while also teeing them, see:
|
||||||
|
# https://unix.stackexchange.com/questions/6430/how-to-redirect-stderr-and-stdout-to-different-files-and-also-display-in-termina/6431#6431
|
||||||
|
|
||||||
|
|
||||||
|
# Starts headless VNC server, backgrounding it.
|
||||||
|
def start_xvnc():
|
||||||
|
xvnc_command = " ".join(
|
||||||
|
[
|
||||||
|
"Xvnc",
|
||||||
|
":0",
|
||||||
|
"-iglx",
|
||||||
|
"-auth /root/.Xauthority",
|
||||||
|
"-geometry 1240x900",
|
||||||
|
"-depth 24",
|
||||||
|
"-rfbwait 5000",
|
||||||
|
"-deferupdate 1",
|
||||||
|
"-verbose",
|
||||||
|
"-securitytypes none",
|
||||||
|
# We don't enforce localhost listening such that we
|
||||||
|
# can connect from outside the VM using
|
||||||
|
# env QEMU_NET_OPTS=hostfwd=tcp::5900-:5900 $(nix-build nixos/tests/turbovnc-headless-server.nix -A driver)/bin/nixos-test-driver
|
||||||
|
# for testing purposes, and so that we can in the future
|
||||||
|
# add another test case that connects the TurboVNC client.
|
||||||
|
# "-localhost",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
machine.execute(
|
||||||
|
# Note trailing & for backgrounding.
|
||||||
|
f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr &",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Waits until the server log message that tells us that GLX is ready
|
||||||
|
# (requires `-verbose` above), avoiding screenshoting racing below.
|
||||||
|
def wait_until_xvnc_glx_ready():
|
||||||
|
machine.wait_until_succeeds("test -f /tmp/Xvnc.stderr")
|
||||||
|
wait_until_terminated_or_succeeds(
|
||||||
|
termination_check_shell_command="pidof Xvnc",
|
||||||
|
success_check_shell_command="grep 'GLX: Initialized DRISWRAST' /tmp/Xvnc.stderr",
|
||||||
|
get_detail_message_fn=lambda: "Contents of /tmp/Xvnc.stderr:\n"
|
||||||
|
+ machine.succeed("cat /tmp/Xvnc.stderr"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Checks that we detect glxgears failing when
|
||||||
|
# `LIBGL_DRIVERS_PATH=/nonexistent` is set
|
||||||
|
# (in which case software rendering should not work).
|
||||||
|
def test_glxgears_failing_with_bad_driver_path():
|
||||||
|
machine.execute(
|
||||||
|
# Note trailing & for backgrounding.
|
||||||
|
"(env DISPLAY=:0 LIBGL_DRIVERS_PATH=/nonexistent glxgears -info | tee /tmp/glxgears-should-fail.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears-should-fail.stderr &"
|
||||||
|
)
|
||||||
|
machine.wait_until_succeeds("test -f /tmp/glxgears-should-fail.stderr")
|
||||||
|
wait_until_terminated_or_succeeds(
|
||||||
|
termination_check_shell_command="pidof glxgears",
|
||||||
|
success_check_shell_command="grep 'libGL error: failed to load driver: swrast' /tmp/glxgears-should-fail.stderr",
|
||||||
|
get_detail_message_fn=lambda: "Contents of /tmp/glxgears-should-fail.stderr:\n"
|
||||||
|
+ machine.succeed("cat /tmp/glxgears-should-fail.stderr"),
|
||||||
|
)
|
||||||
|
machine.wait_until_fails("pidof glxgears")
|
||||||
|
|
||||||
|
|
||||||
|
# Starts glxgears, backgrounding it. Waits until it prints the `GL_RENDERER`.
|
||||||
|
# Does not quit glxgears.
|
||||||
|
def test_glxgears_prints_renderer():
|
||||||
|
machine.execute(
|
||||||
|
# Note trailing & for backgrounding.
|
||||||
|
"(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr &"
|
||||||
|
)
|
||||||
|
machine.wait_until_succeeds("test -f /tmp/glxgears.stderr")
|
||||||
|
wait_until_terminated_or_succeeds(
|
||||||
|
termination_check_shell_command="pidof glxgears",
|
||||||
|
success_check_shell_command="grep 'GL_RENDERER' /tmp/glxgears.stdout",
|
||||||
|
get_detail_message_fn=lambda: "Contents of /tmp/glxgears.stderr:\n"
|
||||||
|
+ machine.succeed("cat /tmp/glxgears.stderr"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
with subtest("Start Xvnc"):
|
||||||
|
start_xvnc()
|
||||||
|
wait_until_xvnc_glx_ready()
|
||||||
|
|
||||||
|
with subtest("Ensure bad driver path makes glxgears fail"):
|
||||||
|
test_glxgears_failing_with_bad_driver_path()
|
||||||
|
|
||||||
|
with subtest("Run 3D application (glxgears)"):
|
||||||
|
test_glxgears_prints_renderer()
|
||||||
|
|
||||||
|
# Take screenshot; should display the glxgears.
|
||||||
|
machine.succeed("scrot --display :0 /tmp/glxgears.png")
|
||||||
|
|
||||||
|
# Copy files down.
|
||||||
|
machine.copy_from_vm("/tmp/glxgears.png")
|
||||||
|
machine.copy_from_vm("/tmp/glxgears.stdout")
|
||||||
|
machine.copy_from_vm("/tmp/glxgears-should-fail.stdout")
|
||||||
|
machine.copy_from_vm("/tmp/glxgears-should-fail.stderr")
|
||||||
|
machine.copy_from_vm("/tmp/Xvnc.stdout")
|
||||||
|
machine.copy_from_vm("/tmp/Xvnc.stderr")
|
||||||
|
'';
|
||||||
|
|
||||||
|
})
|
|
@ -1,6 +1,7 @@
|
||||||
{ lib
|
{ lib
|
||||||
, stdenv
|
, stdenv
|
||||||
, fetchFromGitHub
|
, fetchFromGitHub
|
||||||
|
, nixosTests
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
, cmake
|
, cmake
|
||||||
|
@ -101,6 +102,8 @@ stdenv.mkDerivation rec {
|
||||||
--prefix PATH : ${lib.makeBinPath [ openssh ]}
|
--prefix PATH : ${lib.makeBinPath [ openssh ]}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
passthru.tests.turbovnc-headless-server = nixosTests.turbovnc-headless-server;
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
homepage = "https://turbovnc.org/";
|
homepage = "https://turbovnc.org/";
|
||||||
license = lib.licenses.gpl2Plus;
|
license = lib.licenses.gpl2Plus;
|
||||||
|
|
Loading…
Reference in a new issue