Merge pull request #127933 from rnhmjoj/qemu-restoration

Qemu restoration
This commit is contained in:
Timothy DeHerrera
2021-09-28 21:35:23 -06:00
committed by GitHub
10 changed files with 301 additions and 91 deletions

View File

@@ -10,10 +10,10 @@
{ config, lib, pkgs, options, ... }:
with lib;
with import ../../lib/qemu-flags.nix { inherit pkgs; };
let
qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
cfg = config.virtualisation;
@@ -75,7 +75,7 @@ let
in
"-drive ${driveOpts} ${device}";
drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives);
# Creates a device name from a 1-based a numerical index, e.g.
@@ -108,7 +108,7 @@ let
''
#! ${pkgs.runtimeShell}
NIX_DISK_IMAGE=$(readlink -f ''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}})
NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}")
if ! test -e "$NIX_DISK_IMAGE"; then
${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
@@ -121,26 +121,29 @@ let
fi
# Create a directory for exchanging data with the VM.
mkdir -p $TMPDIR/xchg
mkdir -p "$TMPDIR/xchg"
${if cfg.useBootLoader then ''
${lib.optionalString cfg.useBootLoader
''
# Create a writable copy/snapshot of the boot disk.
# A writable boot disk can be booted from automatically.
${qemu}/bin/qemu-img create -f qcow2 -b ${bootDisk}/disk.img $TMPDIR/disk.img || exit 1
${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img" || exit 1
NIX_EFI_VARS=$(readlink -f ''${NIX_EFI_VARS:-${cfg.efiVars}})
NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}")
${if cfg.useEFIBoot then ''
${lib.optionalString cfg.useEFIBoot
''
# VM needs writable EFI vars
if ! test -e "$NIX_EFI_VARS"; then
cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS" || exit 1
chmod 0644 "$NIX_EFI_VARS" || exit 1
fi
'' else ""}
'' else ""}
''}
''}
cd $TMPDIR
idx=0
cd "$TMPDIR" || exit 1
${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
${flip concatMapStrings cfg.emptyDiskImages (size: ''
if ! test -e "empty$idx.qcow2"; then
${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
@@ -149,17 +152,18 @@ let
'')}
# Start QEMU.
exec ${qemuBinary qemu} \
exec ${qemu-common.qemuBinary qemu} \
-name ${config.system.name} \
-m ${toString config.virtualisation.memorySize} \
-smp ${toString config.virtualisation.cores} \
-device virtio-rng-pci \
${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
-virtfs local,path=/nix/store,security_model=none,mount_tag=store \
-virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
-virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
${concatStringsSep " \\\n "
(mapAttrsToList
(tag: share: "-virtfs local,path=${share.source},security_model=none,mount_tag=${tag}")
config.virtualisation.sharedDirectories)} \
${drivesCmdLine config.virtualisation.qemu.drives} \
${toString config.virtualisation.qemu.options} \
${concatStringsSep " \\\n " config.virtualisation.qemu.options} \
$QEMU_OPTS \
"$@"
'';
@@ -270,20 +274,21 @@ in
virtualisation.memorySize =
mkOption {
type = types.ints.positive;
default = 384;
description =
''
Memory size (M) of virtual machine.
The memory size in megabytes of the virtual machine.
'';
};
virtualisation.msize =
mkOption {
default = null;
type = types.nullOr types.ints.unsigned;
type = types.ints.positive;
default = 16384;
description =
''
msize (maximum packet size) option passed to 9p file systems, in
The msize (maximum packet size) option passed to 9p file systems, in
bytes. Increasing this should increase performance significantly,
at the cost of higher RAM usage.
'';
@@ -291,15 +296,17 @@ in
virtualisation.diskSize =
mkOption {
type = types.nullOr types.ints.positive;
default = 512;
description =
''
Disk size (M) of virtual machine.
The disk size in megabytes of the virtual machine.
'';
};
virtualisation.diskImage =
mkOption {
type = types.str;
default = "./${config.system.name}.qcow2";
description =
''
@@ -311,7 +318,7 @@ in
virtualisation.bootDevice =
mkOption {
type = types.str;
type = types.path;
example = "/dev/vda";
description =
''
@@ -321,8 +328,8 @@ in
virtualisation.emptyDiskImages =
mkOption {
type = types.listOf types.ints.positive;
default = [];
type = types.listOf types.int;
description =
''
Additional disk images to provide to the VM. The value is
@@ -333,6 +340,7 @@ in
virtualisation.graphics =
mkOption {
type = types.bool;
default = true;
description =
''
@@ -342,10 +350,20 @@ in
'';
};
virtualisation.resolution =
mkOption {
type = options.services.xserver.resolutions.type.nestedTypes.elemType;
default = { x = 1024; y = 768; };
description =
''
The resolution of the virtual machine display.
'';
};
virtualisation.cores =
mkOption {
type = types.ints.positive;
default = 1;
type = types.int;
description =
''
Specify the number of cores the guest is permitted to use.
@@ -354,8 +372,34 @@ in
'';
};
virtualisation.sharedDirectories =
mkOption {
type = types.attrsOf
(types.submodule {
options.source = mkOption {
type = types.str;
description = "The path of the directory to share, can be a shell variable";
};
options.target = mkOption {
type = types.path;
description = "The mount point of the directory inside the virtual machine";
};
});
default = { };
example = {
my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
};
description =
''
An attributes set of directories that will be shared with the
virtual machine using VirtFS (9P filesystem over VirtIO).
The attribute name will be used as the 9P mount tag.
'';
};
virtualisation.pathsInNixDB =
mkOption {
type = types.listOf types.path;
default = [];
description =
''
@@ -367,8 +411,78 @@ in
'';
};
virtualisation.forwardPorts = mkOption {
type = types.listOf
(types.submodule {
options.from = mkOption {
type = types.enum [ "host" "guest" ];
default = "host";
description =
''
Controls the direction in which the ports are mapped:
- <literal>"host"</literal> means traffic from the host ports
is forwarded to the given guest port.
- <literal>"guest"</literal> means traffic from the guest ports
is forwarded to the given host port.
'';
};
options.proto = mkOption {
type = types.enum [ "tcp" "udp" ];
default = "tcp";
description = "The protocol to forward.";
};
options.host.address = mkOption {
type = types.str;
default = "";
description = "The IPv4 address of the host.";
};
options.host.port = mkOption {
type = types.port;
description = "The host port to be mapped.";
};
options.guest.address = mkOption {
type = types.str;
default = "";
description = "The IPv4 address on the guest VLAN.";
};
options.guest.port = mkOption {
type = types.port;
description = "The guest port to be mapped.";
};
});
default = [];
example = lib.literalExample
''
[ # forward local port 2222 -> 22, to ssh into the VM
{ from = "host"; host.port = 2222; guest.port = 22; }
# forward local port 80 -> 10.0.2.10:80 in the VLAN
{ from = "guest";
guest.address = "10.0.2.10"; guest.port = 80;
host.address = "127.0.0.1"; host.port = 80;
}
]
'';
description =
''
When using the SLiRP user networking (default), this option allows to
forward ports to/from the host/guest.
<warning><para>
If the NixOS firewall on the virtual machine is enabled, you also
have to open the guest ports to enable the traffic between host and
guest.
</para></warning>
<note><para>Currently QEMU supports only IPv4 forwarding.</para></note>
'';
};
virtualisation.vlans =
mkOption {
type = types.listOf types.ints.unsigned;
default = [ 1 ];
example = [ 1 2 ];
description =
@@ -386,6 +500,7 @@ in
virtualisation.writableStore =
mkOption {
type = types.bool;
default = true; # FIXME
description =
''
@@ -397,6 +512,7 @@ in
virtualisation.writableStoreUseTmpfs =
mkOption {
type = types.bool;
default = true;
description =
''
@@ -407,6 +523,7 @@ in
networking.primaryIPAddress =
mkOption {
type = types.str;
default = "";
internal = true;
description = "Primary IP address used in /etc/hosts.";
@@ -423,7 +540,7 @@ in
options =
mkOption {
type = types.listOf types.unspecified;
type = types.listOf types.str;
default = [];
example = [ "-vga std" ];
description = "Options passed to QEMU.";
@@ -432,7 +549,7 @@ in
consoles = mkOption {
type = types.listOf types.str;
default = let
consoles = [ "${qemuSerialDevice},115200n8" "tty0" ];
consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ];
in if cfg.graphics then consoles else reverseList consoles;
example = [ "console=tty1" ];
description = ''
@@ -448,17 +565,18 @@ in
networkingOptions =
mkOption {
default = [
"-net nic,netdev=user.0,model=virtio"
"-netdev user,id=user.0\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
];
type = types.listOf types.str;
default = [ ];
example = [
"-net nic,netdev=user.0,model=virtio"
"-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
];
description = ''
Networking-related command-line options that should be passed to qemu.
The default is to use userspace networking (slirp).
The default is to use userspace networking (SLiRP).
If you override this option, be advised to keep
''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the default)
''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the example)
to keep the default runtime behaviour.
'';
};
@@ -472,16 +590,16 @@ in
diskInterface =
mkOption {
type = types.enum [ "virtio" "scsi" "ide" ];
default = "virtio";
example = "scsi";
type = types.enum [ "virtio" "scsi" "ide" ];
description = "The interface used for the virtual hard disks.";
};
guestAgent.enable =
mkOption {
default = true;
type = types.bool;
default = true;
description = ''
Enable the Qemu guest agent.
'';
@@ -490,6 +608,7 @@ in
virtualisation.useBootLoader =
mkOption {
type = types.bool;
default = false;
description =
''
@@ -504,6 +623,7 @@ in
virtualisation.useEFIBoot =
mkOption {
type = types.bool;
default = false;
description =
''
@@ -515,6 +635,7 @@ in
virtualisation.efiVars =
mkOption {
type = types.str;
default = "./${config.system.name}-efi-vars.fd";
description =
''
@@ -525,8 +646,8 @@ in
virtualisation.bios =
mkOption {
default = null;
type = types.nullOr types.package;
default = null;
description =
''
An alternate BIOS (such as <package>qboot</package>) with which to start the VM.
@@ -539,6 +660,25 @@ in
config = {
assertions =
lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule:
[
{ assertion = rule.from == "guest" -> rule.proto == "tcp";
message =
''
Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto:
Guest forwarding supports only TCP connections.
'';
}
{ assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address;
message =
''
Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address:
The address must be in the default VLAN (10.0.2.0/24).
'';
}
]));
# Note [Disk layout with `useBootLoader`]
#
# If `useBootLoader = true`, we configure 2 drives:
@@ -560,6 +700,7 @@ in
then driveDeviceName 2 # second disk
else cfg.bootDevice
);
boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
boot.initrd.extraUtilsCommands =
''
@@ -618,6 +759,28 @@ in
virtualisation.pathsInNixDB = [ config.system.build.toplevel ];
virtualisation.sharedDirectories = {
nix-store = { source = "/nix/store"; target = "/nix/store"; };
xchg = { source = ''"$TMPDIR"/xchg''; target = "/tmp/xchg"; };
shared = { source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"''; target = "/tmp/shared"; };
};
virtualisation.qemu.networkingOptions =
let
forwardingOptions = flip concatMapStrings cfg.forwardPorts
({ proto, from, host, guest }:
if from == "host"
then "hostfwd=${proto}:${host.address}:${toString host.port}-" +
"${guest.address}:${toString guest.port},"
else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" +
"cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
);
in
[
"-net nic,netdev=user.0,model=virtio"
"-netdev user,id=user.0,${forwardingOptions}\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
];
# FIXME: Consolidate this one day.
virtualisation.qemu.options = mkMerge [
(mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
@@ -646,7 +809,7 @@ in
virtualisation.qemu.drives = mkMerge [
[{
name = "root";
file = "$NIX_DISK_IMAGE";
file = ''"$NIX_DISK_IMAGE"'';
driveExtraOpts.cache = "writeback";
driveExtraOpts.werror = "report";
}]
@@ -655,7 +818,7 @@ in
# note [Disk layout with `useBootLoader`].
{
name = "boot";
file = "$TMPDIR/disk.img";
file = ''"$TMPDIR"/disk.img'';
driveExtraOpts.media = "disk";
deviceExtraOpts.bootindex = "1";
}
@@ -672,15 +835,26 @@ in
# configuration, where the regular value for the `fileSystems'
# attribute should be disregarded for the purpose of building a VM
# test image (since those filesystems don't exist in the VM).
fileSystems = mkVMOverride (
cfg.fileSystems //
{ "/".device = cfg.bootDevice;
${if cfg.writableStore then "/nix/.ro-store" else "/nix/store"} =
{ device = "store";
fsType = "9p";
options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
neededForBoot = true;
};
fileSystems =
let
mkSharedDir = tag: share:
{
name =
if tag == "nix-store" && cfg.writableStore
then "/nix/.ro-store"
else share.target;
value.device = tag;
value.fsType = "9p";
value.neededForBoot = true;
value.options =
[ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ]
++ lib.optional (tag == "nix-store") "cache=loose";
};
in
mkVMOverride (cfg.fileSystems //
{
"/".device = cfg.bootDevice;
"/tmp" = mkIf config.boot.tmpOnTmpfs
{ device = "tmpfs";
fsType = "tmpfs";
@@ -688,32 +862,20 @@ in
# Sync with systemd's tmp.mount;
options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
};
"/tmp/xchg" =
{ device = "xchg";
fsType = "9p";
options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
neededForBoot = true;
};
"/tmp/shared" =
{ device = "shared";
fsType = "9p";
options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
neededForBoot = true;
};
} // optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs)
{ "/nix/.rw-store" =
"/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs)
{ fsType = "tmpfs";
options = [ "mode=0755" ];
neededForBoot = true;
};
} // optionalAttrs cfg.useBootLoader
{ "/boot" =
"/boot" = mkIf cfg.useBootLoader
# see note [Disk layout with `useBootLoader`]
{ device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
fsType = "vfat";
noCheck = true; # fsck fails on a r/o filesystem
};
});
} // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
swapDevices = mkVMOverride [ ];
boot.initrd.luks.devices = mkVMOverride {};
@@ -734,7 +896,7 @@ in
# video driver the host uses.
services.xserver.videoDrivers = mkVMOverride [ "modesetting" ];
services.xserver.defaultDepth = mkVMOverride 0;
services.xserver.resolutions = mkVMOverride [ { x = 1024; y = 768; } ];
services.xserver.resolutions = mkVMOverride [ cfg.resolution ];
services.xserver.monitorSection =
''
# Set a higher refresh rate so that resolutions > 800x600 work.