at 25.11-pre 8.4 kB view raw
1# This module allows the test driver to connect to the virtual machine 2# via a root shell attached to port 514. 3 4{ 5 options, 6 config, 7 lib, 8 pkgs, 9 ... 10}: 11 12with lib; 13 14let 15 cfg = config.testing; 16 17 qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; }; 18 19 backdoorService = { 20 requires = [ 21 "dev-hvc0.device" 22 "dev-${qemu-common.qemuSerialDevice}.device" 23 ]; 24 after = [ 25 "dev-hvc0.device" 26 "dev-${qemu-common.qemuSerialDevice}.device" 27 ]; 28 script = '' 29 export USER=root 30 export HOME=/root 31 export DISPLAY=:0.0 32 33 # Determine if this script is ran with nounset 34 strict="false" 35 if set -o | grep --quiet --perl-regexp "nounset\s+on"; then 36 strict="true" 37 fi 38 39 if [[ -e /etc/profile ]]; then 40 # TODO: Currently shell profiles are not checked at build time, 41 # so we need to unset stricter options to source them 42 set +o nounset 43 # shellcheck disable=SC1091 44 source /etc/profile 45 [ "$strict" = "true" ] && set -o nounset 46 fi 47 48 # Don't use a pager when executing backdoor 49 # actions. Because we use a tty, commands like systemctl 50 # or nix-store get confused into thinking they're running 51 # interactively. 52 export PAGER= 53 54 cd /tmp 55 exec < /dev/hvc0 > /dev/hvc0 56 while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done 57 echo "connecting to host..." >&2 58 stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion 59 # The following line is essential since it signals to 60 # the test driver that the shell is ready. 61 # See: the connect method in the Machine class. 62 echo "Spawning backdoor root shell..." 63 # Passing the terminal device makes bash run non-interactively. 64 # Otherwise we get errors on the terminal because bash tries to 65 # setup things like job control. 66 # Note: calling bash explicitly here instead of sh makes sure that 67 # we can also run non-NixOS guests during tests. This, however, is 68 # mostly futureproofing as the test instrumentation is still very 69 # tightly coupled to NixOS. 70 PS1="" exec ${pkgs.coreutils}/bin/env bash --norc /dev/hvc0 71 ''; 72 serviceConfig.KillSignal = "SIGHUP"; 73 }; 74 75in 76 77{ 78 79 options.testing = { 80 81 initrdBackdoor = lib.mkEnableOption '' 82 backdoor.service in initrd. Requires 83 boot.initrd.systemd.enable to be enabled. Boot will pause in 84 stage 1 at initrd.target, and will listen for commands from the 85 Machine python interface, just like stage 2 normally does. This 86 enables commands to be sent to test and debug stage 1. Use 87 machine.switch_root() to leave stage 1 and proceed to stage 2 88 ''; 89 }; 90 91 config = { 92 93 assertions = [ 94 { 95 assertion = cfg.initrdBackdoor -> config.boot.initrd.systemd.enable; 96 message = '' 97 testing.initrdBackdoor requires boot.initrd.systemd.enable to be enabled. 98 ''; 99 } 100 ]; 101 102 systemd.services.backdoor = lib.mkMerge [ 103 backdoorService 104 { 105 wantedBy = [ "multi-user.target" ]; 106 } 107 ]; 108 109 boot.initrd.systemd = lib.mkMerge [ 110 { 111 contents."/etc/systemd/journald.conf".text = '' 112 [Journal] 113 ForwardToConsole=yes 114 TTYPath=/dev/${qemu-common.qemuSerialDevice} 115 MaxLevelConsole=debug 116 ''; 117 118 extraConfig = config.systemd.extraConfig; 119 } 120 121 (lib.mkIf cfg.initrdBackdoor { 122 # Implemented in machine.switch_root(). Suppress the unit by 123 # making it a noop without removing it, which would break 124 # initrd-parse-etc.service 125 services.initrd-cleanup.serviceConfig.ExecStart = [ 126 # Reset 127 "" 128 # noop 129 "/bin/true" 130 ]; 131 132 services.backdoor = lib.mkMerge [ 133 backdoorService 134 { 135 # TODO: Both stage 1 and stage 2 should use these same 136 # settings. But a lot of existing tests rely on 137 # backdoor.service having default orderings, 138 # e.g. systemd-boot.update relies on /boot being mounted 139 # as soon as backdoor starts. But it can be useful for 140 # backdoor to start even earlier. 141 wantedBy = [ "sysinit.target" ]; 142 unitConfig.DefaultDependencies = false; 143 conflicts = [ 144 "shutdown.target" 145 "initrd-switch-root.target" 146 ]; 147 before = [ 148 "shutdown.target" 149 "initrd-switch-root.target" 150 ]; 151 } 152 ]; 153 154 storePaths = [ 155 "${pkgs.coreutils}/bin/env" 156 ]; 157 }) 158 ]; 159 160 # Prevent agetty from being instantiated on the serial device, since it 161 # interferes with the backdoor (writes to it will randomly fail 162 # with EIO). Likewise for hvc0. 163 systemd.services."serial-getty@${qemu-common.qemuSerialDevice}".enable = false; 164 systemd.services."serial-getty@hvc0".enable = false; 165 166 # Only set these settings when the options exist. Some tests (e.g. those 167 # that do not specify any nodes, or an empty attr set as nodes) will not 168 # have the QEMU module loaded and thuse these options can't and should not 169 # be set. 170 virtualisation = lib.optionalAttrs (options ? virtualisation.qemu) { 171 qemu = { 172 # NOTE: optionalAttrs 173 # test-instrumentation.nix appears to be used without qemu-vm.nix, so 174 # we avoid defining attributes if not possible. 175 # TODO: refactor such that test-instrumentation can import qemu-vm 176 package = lib.mkDefault pkgs.qemu_test; 177 }; 178 }; 179 180 boot.kernel.sysctl = { 181 "kernel.hung_task_timeout_secs" = 600; 182 # Panic on out-of-memory conditions rather than letting the 183 # OOM killer randomly get rid of processes, since this leads 184 # to failures that are hard to diagnose. 185 "vm.panic_on_oom" = lib.mkDefault 2; 186 }; 187 188 boot.kernelParams = [ 189 "console=${qemu-common.qemuSerialDevice}" 190 "console=tty0" 191 # Panic if an error occurs in stage 1 (rather than waiting for 192 # user intervention). 193 "panic=1" 194 "boot.panic_on_fail" 195 # Using acpi_pm as a clock source causes the guest clock to 196 # slow down under high host load. This is usually a bad 197 # thing, but for VM tests it should provide a bit more 198 # determinism (e.g. if the VM runs at lower speed, then 199 # timeouts in the VM should also be delayed). 200 "clocksource=acpi_pm" 201 ]; 202 203 # `xwininfo' is used by the test driver to query open windows. 204 environment.systemPackages = [ pkgs.xorg.xwininfo ]; 205 206 # Log everything to the serial console. 207 services.journald.extraConfig = '' 208 ForwardToConsole=yes 209 TTYPath=/dev/${qemu-common.qemuSerialDevice} 210 MaxLevelConsole=debug 211 ''; 212 213 systemd.extraConfig = '' 214 # Don't clobber the console with duplicate systemd messages. 215 ShowStatus=no 216 # Allow very slow start 217 DefaultTimeoutStartSec=300 218 DefaultDeviceTimeoutSec=300 219 ''; 220 systemd.user.extraConfig = '' 221 # Allow very slow start 222 DefaultTimeoutStartSec=300 223 DefaultDeviceTimeoutSec=300 224 ''; 225 226 boot.consoleLogLevel = 7; 227 228 # Prevent tests from accessing the Internet. 229 networking.defaultGateway = mkOverride 150 null; 230 networking.nameservers = mkOverride 150 [ ]; 231 232 system.requiredKernelConfig = with config.lib.kernelConfig; [ 233 (isYes "SERIAL_8250_CONSOLE") 234 (isYes "SERIAL_8250") 235 (isEnabled "VIRTIO_CONSOLE") 236 ]; 237 238 networking.usePredictableInterfaceNames = false; 239 240 # Make it easy to log in as root when running the test interactively. 241 # This needs to be a file because of a quirk in systemd credentials, 242 # where you cannot specify an empty string as a value. systemd-sysusers 243 # uses credentials to set passwords on users. 244 users.users.root.hashedPasswordFile = mkOverride 150 "${pkgs.writeText "hashed-password.root" ""}"; 245 246 services.displayManager.logToJournal = true; 247 248 services.logrotate.enable = mkOverride 150 false; 249 250 # Make sure we use the Guest Agent from the QEMU package for testing 251 # to reduce the closure size required for the tests. 252 services.qemuGuest.package = pkgs.qemu_test.ga; 253 254 # Squelch warning about unset system.stateVersion 255 system.stateVersion = (lib.mkOverride 1200) lib.trivial.release; 256 }; 257 258}