diff --git a/modules/libvirtd/domain.nix b/modules/libvirtd/domain.nix index 7b788d4..52a0138 100644 --- a/modules/libvirtd/domain.nix +++ b/modules/libvirtd/domain.nix @@ -447,109 +447,125 @@ let }; }; - domainType = types.submodule { - options = { - memory = mkOption { - type = memoryOptionsType; - description = '' - Memory configuration. See https://libvirt.org/formatdomain.html#memory-allocation for details. - ''; - }; - - os = mkOption { - type = osOptionsType; - default = { }; - description = '' - OS configuration. See https://libvirt.org/formatdomain.html#operating-system-booting for details. - ''; - }; - - vcpu = mkOption { - type = vcpuOptionsType; - description = mdDoc '' - vCPU allocation. See https://libvirt.org/formatdomain.html#cpu-allocation for details - ''; - }; - - cputune = mkOption { - type = types.nullOr cputuneOptionsType; - default = { }; - description = mdDoc '' - CPU tuning options. See https://libvirt.org/formatdomain.html#cpu-tuning for details - ''; - }; - - cpu = mkOption { - type = cpuOptionsType; - default = { }; - description = mdDoc '' - CPU and topology settings. See https://libvirt.org/formatdomain.html#cpu-model-and-topology for details - ''; - }; - - input = mkOption { - type = inputOptionsType; - default = { }; - description = mdDoc '' - Configure input devices - ''; - }; - - spice = mkOption { - type = spiceOptionsType; - description = mdDoc '' - Configure spice - ''; - }; - - pciHostDevices = mkOption { - type = types.listOf pciHostdevType; - default = [ ]; - description = mdDoc '' - PCI host devices - ''; - }; + domainType = with types; + (submodule ({ name, config, options, ... }: { + options = { + config = mkOption { + type = nullOr (submodule { + options = { + memory = mkOption { + type = memoryOptionsType; + description = '' + Memory configuration. See https://libvirt.org/formatdomain.html#memory-allocation for details. + ''; + }; + + os = mkOption { + type = osOptionsType; + default = { }; + description = '' + OS configuration. See https://libvirt.org/formatdomain.html#operating-system-booting for details. + ''; + }; + + vcpu = mkOption { + type = vcpuOptionsType; + description = mdDoc '' + vCPU allocation. See https://libvirt.org/formatdomain.html#cpu-allocation for details + ''; + }; + + cputune = mkOption { + type = types.nullOr cputuneOptionsType; + default = { }; + description = mdDoc '' + CPU tuning options. See https://libvirt.org/formatdomain.html#cpu-tuning for details + ''; + }; + + cpu = mkOption { + type = cpuOptionsType; + default = { }; + description = mdDoc '' + CPU and topology settings. See https://libvirt.org/formatdomain.html#cpu-model-and-topology for details + ''; + }; + + input = mkOption { + type = inputOptionsType; + default = { }; + description = mdDoc '' + Configure input devices + ''; + }; + + spice = mkOption { + type = spiceOptionsType; + description = mdDoc '' + Configure spice + ''; + }; + + pciHostDevices = mkOption { + type = types.listOf pciHostdevType; + default = [ ]; + description = mdDoc '' + PCI host devices + ''; + }; + + networkInterfaces = mkOption { + type = types.listOf networkInterfaceType; + default = [ ]; + description = mdDoc '' + Network interfaces + ''; + }; + + cdroms = mkOption { + type = types.listOf cdromType; + default = [ ]; + description = mdDoc '' + CDROMs to attach to the domain + ''; + }; + + kvmfr = mkOption { + type = types.nullOr kvmfrOptionsType; + description = mdDoc '' + kvmfr settings + ''; + }; + }; - networkInterfaces = mkOption { - type = types.listOf networkInterfaceType; - default = [ ]; - description = mdDoc '' - Network interfaces - ''; - }; + }); - cdroms = mkOption { - type = types.listOf cdromType; - default = [ ]; - description = mdDoc '' - CDROMs to attach to the domain - ''; - }; + default = null; + description = mdDoc '' + Nix definition of the domain. Overrides xml if set. + ''; + }; - kvmfr = mkOption { - type = types.nullOr kvmfrOptionsType; - description = mdDoc '' - kvmfr settings - ''; - }; + xml = mkOption { + type = types.str; + description = mdDoc '' + Raw XML definition of the domain. Will be overridden by config if set. + ''; + }; - extraXml = mkOption { - type = types.str; - default = ""; - description = mdDoc '' - extra XML appended to the generated domain - ''; + autostart = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Whether to start the domain on boot. + ''; + }; }; - autostart = mkOption { - type = types.bool; - default = false; - description = mdDoc '' - Whether to start the domain on boot. - ''; + config = { + xml = mkIf (config.config != null) (mkDomainXml name config.config); }; - }; - }; + })); mkDomainXml = let mkMemoryXml = memory: '' @@ -812,17 +828,14 @@ let ${optionalString (definition.kvmfr != null) mkKvmfrXml definition.kvmfr} - ${definition.extraXml} ''; - mkDomainXmlPackage = name: domain: + mkDomainXmlPackage = name: config: pkgs.runCommand "libvirt-domain-${name}.xml" { } '' mkdir $out - - echo '${mkDomainXml name domain}' > domain.xml + echo '${config.xml}' > domain.xml ${pkgs.libxml2}/bin/xmllint --format domain.xml > $out/domain.xml - cat $out/domain.xml ${pkgs.libvirt}/bin/virt-xml-validate $out/domain.xml ''; @@ -839,7 +852,7 @@ let commands = map (name: '' ln -s /var/lib/libvirt/qemu/${name}.xml /var/lib/libvirt/qemu/autostart/${name}.xml '') domainsToAutostart; - in concatStringsSep "\n" commands; + in concatStringsSep "\n" commands; in { options.virtualisation.libvirtd.qemu.domains = { declarative = mkOption { diff --git a/modules/libvirtd/network.nix b/modules/libvirtd/network.nix index 4204ed0..12a416f 100644 --- a/modules/libvirtd/network.nix +++ b/modules/libvirtd/network.nix @@ -6,152 +6,171 @@ let cfg = config.virtualisation.libvirtd.qemu.networks; networkType = with types; - submodule { + (submodule ({ name, config, options, ... }: { options = { - bridge = mkOption { + config = mkOption { type = nullOr (submodule { options = { - name = mkOption { - type = str; - description = mdDoc '' - Defines the name of a bridge device which will be used to - construct the virtual network. The virtual machines will be connected to this bridge device allowing - them to talk to each other. The bridge device may also be connected to the LAN. When defining a new - network with a mode of "nat", "route", or "open" - (or an isolated network with no element), libvirt will automatically generate a unique name - for the bridge device if none is given, and this name will be permanently stored in the network - configuration so that that the same name will be used every time the network is started. For these types - of networks (nat, route, open, and isolated), a bridge name beginning with the prefix "virbr" is - recommended (and that is what is auto-generated), but not enforced. - ''; + bridge = mkOption { + type = (submodule { + options = { + name = mkOption { + type = str; + description = mdDoc '' + Defines the name of a bridge device which will be used to + construct the virtual network. The virtual machines will be connected to this bridge device allowing + them to talk to each other. The bridge device may also be connected to the LAN. When defining a new + network with a mode of "nat", "route", or "open" + (or an isolated network with no element), libvirt will automatically generate a unique name + for the bridge device if none is given, and this name will be permanently stored in the network + configuration so that that the same name will be used every time the network is started. For these types + of networks (nat, route, open, and isolated), a bridge name beginning with the prefix "virbr" is + recommended (and that is what is auto-generated), but not enforced. + ''; + }; + }; + }); }; - }; - }); - default = null; - }; + mtu = mkOption { + type = submodule { + options = { + size = mkOption { + type = nullOr ints.positive; + default = null; + description = mdDoc '' + Specifies the Maximum Transmission Unit (MTU) for the network. In the case of a libvirt-managed network + (one with forward mode of nat, route, open, or no forward element (i.e. an isolated network), this will + be the MTU assigned to the bridge device when libvirt creates it, and thereafter also assigned to all + tap devices created to connect guest interfaces. Network types not specifically mentioned here don't + support having an MTU set in the libvirt network config. If mtu size is unspecified, the default + setting for the type of device being used is assumed (usually 1500). + ''; + }; + }; + }; - mtu = mkOption { - type = submodule { - options = { - size = mkOption { - type = nullOr ints.positive; - default = null; - description = mdDoc '' - Specifies the Maximum Transmission Unit (MTU) for the network. In the case of a libvirt-managed network - (one with forward mode of nat, route, open, or no forward element (i.e. an isolated network), this will - be the MTU assigned to the bridge device when libvirt creates it, and thereafter also assigned to all - tap devices created to connect guest interfaces. Network types not specifically mentioned here don't - support having an MTU set in the libvirt network config. If mtu size is unspecified, the default - setting for the type of device being used is assumed (usually 1500). - ''; + default = { }; }; - }; - }; - default = { }; - }; + forward = mkOption { + type = nullOr (submodule { + options = { + mode = mkOption { + type = enum [ + "nat" + "route" + "open" + "bridge" + "private" + "vepa" + "passthrough" + "hostdev" + ]; + default = "nat"; + description = mdDoc '' + Determines the method of forwarding. If there is no forward element, the network will be isolated from + any other network (unless a guest connected to that network is acting as a router, of course). + The following are valid settings for mode (if there is a forward element but mode + is not specified, mode='nat' is assumed) + ''; + }; - forward = mkOption { - type = nullOr (submodule { - options = { - mode = mkOption { - type = enum [ - "nat" - "route" - "open" - "bridge" - "private" - "vepa" - "passthrough" - "hostdev" - ]; - default = "nat"; - description = mdDoc '' - Determines the method of forwarding. If there is no forward element, the network will be isolated from - any other network (unless a guest connected to that network is acting as a router, of course). - The following are valid settings for mode (if there is a forward element but mode - is not specified, mode='nat' is assumed) - ''; - }; + dev = mkOption { + type = nullOr str; + default = null; + description = mdDoc '' + If set, the firewall rules will restrict forwarding to the named device only. Inbound connections + from other networks are all prohibited; all connections between guests on the same network, and + to/from the host to the guests, are unrestricted and not NATed + ''; + }; + }; + }); - dev = mkOption { - type = nullOr str; default = null; - description = mdDoc '' - If set, the firewall rules will restrict forwarding to the named device only. Inbound connections - from other networks are all prohibited; all connections between guests on the same network, and - to/from the host to the guests, are unrestricted and not NATed - ''; - }; - }; - }); - - default = null; - }; - - ips = mkOption { - type = listOf (submodule { - options = { - address = mkOption { - type = str; - description = mdDoc '' - Defines an IPv4 address in dotted-decimal format, or an IPv6 address in standard colon-separated - hexadecimal format, that will be configured on the bridge device associated with the virtual network. - To the guests this IPv4 address will be their IPv4 default route. For IPv6, the default route is - established via Router Advertisement. - ''; }; - prefix = mkOption { - type = ints.positive; - description = mdDoc '' - Specifies the significant bits of the network address. - ''; - }; - - family = mkOption { - type = enum [ "ipv4" "ipv6" ]; - default = "ipv4"; - description = mdDoc '' - Used to specify the type of address - ipv4 or ipv6; if no family is given, ipv4 is assumed. - ''; - }; - - dhcpRanges = mkOption { + ips = mkOption { type = listOf (submodule { options = { - start = mkOption { + address = mkOption { type = str; description = mdDoc '' - Lower boundary of the DHCP range. + Defines an IPv4 address in dotted-decimal format, or an IPv6 address in standard colon-separated + hexadecimal format, that will be configured on the bridge device associated with the virtual network. + To the guests this IPv4 address will be their IPv4 default route. For IPv6, the default route is + established via Router Advertisement. ''; }; - end = mkOption { - type = str; + prefix = mkOption { + type = ints.positive; + description = mdDoc '' + Specifies the significant bits of the network address. + ''; + }; + + family = mkOption { + type = enum [ "ipv4" "ipv6" ]; + default = "ipv4"; description = mdDoc '' - Upper boundary of the DHCP range. + Used to specify the type of address - ipv4 or ipv6; if no family is given, ipv4 is assumed. ''; }; + + dhcpRanges = mkOption { + type = listOf (submodule { + options = { + start = mkOption { + type = str; + description = mdDoc '' + Lower boundary of the DHCP range. + ''; + }; + + end = mkOption { + type = str; + description = mdDoc '' + Upper boundary of the DHCP range. + ''; + }; + }; + }); + + default = [ ]; + }; }; }); - - default = [ ]; }; }; }); + default = null; + description = mdDoc '' + Nix definition of the network. Overrides xml if set. + ''; + }; + + xml = mkOption { + type = str; + description = mdDoc '' + Raw XML definition of the network. Will be overridden by config, if set. + ''; }; autostart = mkOption { - type = types.bool; + type = bool; default = false; description = mdDoc '' Whether to start the domain on boot. ''; }; }; - }; + + config = { + xml = mkIf (config.config != null) (mkNetworkXml name config.config); + }; + })); mkNetworkXml = name: config: '' @@ -195,9 +214,8 @@ let mkNetworkXmlPackage = name: config: pkgs.runCommand "libvirt-network-${name}.xml" { } '' mkdir $out - echo '${mkNetworkXml name config}' > network.xml + echo '${config.xml}' > network.xml ${pkgs.libxml2}/bin/xmllint --format network.xml > $out/network.xml - cat $out/network.xml ${pkgs.libvirt}/bin/virt-xml-validate $out/network.xml ''; diff --git a/tests/libvirtd/domain.nix b/tests/libvirtd/domain.nix index a2588ce..c56b5a5 100644 --- a/tests/libvirtd/domain.nix +++ b/tests/libvirtd/domain.nix @@ -18,76 +18,101 @@ pkgs.nixosTest ({ domains = { win10 = { - memory = { + config = { memory = { - value = 1; - unit = "G"; - }; - - disableBallooning = true; - useHugepages = false; - }; + memory = { + value = 1; + unit = "G"; + }; - os.enableBootmenu = true; + disableBallooning = true; + useHugepages = false; + }; - vcpu = { - count = 2; - placement = "static"; - }; + os.enableBootmenu = true; - cputune = { - vcpupins = [ - { - vcpu = 1; - cpuset = [ 1 ]; - } - { - vcpu = 2; - cpuset = [ 2 ]; - } - ]; - }; + vcpu = { + count = 2; + placement = "static"; + }; - cpu = { - topology = { - sockets = 1; - dies = 1; - cores = 2; - threads = 1; + cputune = { + vcpupins = [ + { + vcpu = 1; + cpuset = [ 1 ]; + } + { + vcpu = 2; + cpuset = [ 2 ]; + } + ]; }; - }; - input = { - virtioMouse = true; - virtioKeyboard = true; - }; + cpu = { + topology = { + sockets = 1; + dies = 1; + cores = 2; + threads = 1; + }; + }; - spice = { - spiceAudio = true; - spicemvcChannel = true; - spiceGraphics = true; - }; + input = { + virtioMouse = true; + virtioKeyboard = true; + }; - pciHostDevices = [{ - sourceAddress = { - bus = "0x04"; - slot = "0x00"; - function = 1; + spice = { + spiceAudio = true; + spicemvcChannel = true; + spiceGraphics = true; }; - }]; - networkInterfaces = [{ sourceNetwork = "default"; }]; + pciHostDevices = [{ + sourceAddress = { + bus = "0x04"; + slot = "0x00"; + function = 1; + }; + }]; - cdroms = [{ - sourceFile = "/opt/someIso.iso"; - bootIndex = 1; - }]; + networkInterfaces = [{ sourceNetwork = "default"; }]; - kvmfr = { - device = "/dev/kvmfr0"; - size = "33554432"; + cdroms = [{ + sourceFile = "/opt/someIso.iso"; + bootIndex = 1; + }]; + + kvmfr = { + device = "/dev/kvmfr0"; + size = "33554432"; + }; }; }; + + rawXml.xml = '' + + rawXml + 131072 + 1 + + hvm + + + /usr/bin/qemu-kvm + + + + + + + + + + + + ''; }; }; }; @@ -97,5 +122,6 @@ pkgs.nixosTest ({ testScript = '' machine.wait_for_unit("libvirtd.service") machine.wait_until_succeeds("[ -f '/var/lib/libvirt/qemu/win10.xml' ]", 10) + machine.wait_until_succeeds("[ -f '/var/lib/libvirt/qemu/rawXml.xml' ]", 10) ''; }) diff --git a/tests/libvirtd/network.nix b/tests/libvirtd/network.nix index 0dfea85..9e101d3 100644 --- a/tests/libvirtd/network.nix +++ b/tests/libvirtd/network.nix @@ -16,33 +16,53 @@ pkgs.nixosTest ({ qemu.networks.declarative = true; qemu.networks.networks = { default = { - forward = { mode = "nat"; }; - - ips = [ - { - family = "ipv4"; - address = "192.168.100.1"; - prefix = 24; - - dhcpRanges = [{ - start = "192.168.100.128"; - end = "192.168.100.254"; - }]; - } - { - family = "ipv6"; - address = "2001:db8:ca2:2::1"; - prefix = 64; - - dhcpRanges = [{ - start = "2001:db8:ca2:2::100"; - end = "2001:db8:ca2:2::1ff"; - }]; - } - ]; + config = { + bridge = { name = "virbr0"; }; + forward = { mode = "nat"; }; + + + ips = [ + { + family = "ipv4"; + address = "192.168.100.1"; + prefix = 24; + + dhcpRanges = [{ + start = "192.168.100.128"; + end = "192.168.100.254"; + }]; + } + { + family = "ipv6"; + address = "2001:db8:ca2:2::1"; + prefix = 64; + + dhcpRanges = [{ + start = "2001:db8:ca2:2::100"; + end = "2001:db8:ca2:2::1ff"; + }]; + } + ]; + }; autostart = true; }; + + rawXml = { + xml = '' + + rawWithOverrides + + + + + + + + + + ''; + }; }; }; }; @@ -51,5 +71,6 @@ pkgs.nixosTest ({ testScript = '' machine.wait_for_unit("libvirtd.service") machine.wait_until_succeeds("[ -f '/var/lib/libvirt/qemu/networks/default.xml' ]", 10) + machine.wait_until_succeeds("[ -f '/var/lib/libvirt/qemu/networks/rawXml.xml' ]", 10) ''; })