Add possibility to override libvirt config

This is the equivalent of "custom config" from R3.x.

fixes QubesOS/qubes-issues#1798
Este commit está contenido en:
Wojtek Porczyk 2016-10-04 11:30:29 +02:00
padre cedd822735
commit 9dc37c1ee7
Se han modificado 5 ficheros con 244 adiciones y 110 borrados

Ver fichero

@ -26,6 +26,7 @@ manpages and API documentation. For primary user documentation, see
.. toctree::
:maxdepth: 1
libvirt
autoxml
manpages/index

114
doc/libvirt.rst Archivo normal
Ver fichero

@ -0,0 +1,114 @@
Custom libvirt config
=====================
Starting from Qubes OS R4.0, libvirt domain config is generated using jinja
templates. Those templates can be overridden by the user in a couple of ways.
A basic knowledge of jinja template language and libvirt xml spec is needed.
.. seealso::
https://libvirt.org/formatdomain.html
Format of the domain XML in libvirt.
http://jinja.pocoo.org/docs/dev/templates/
Template format documentation.
File paths
----------
In order of increasing precedence: the main template, from which the config is
generated is :file:`/usr/share/templates/libvirt/xen.xml`).
The distributor may put a file at
:file:`/usr/share/qubes/template/xen-dist.xml`) to override this file.
User may put a file at either
:file:`/etc/qubes/templates/libvirt/xen-user.xml` or
:file:`/etc/qubes/templates/libvirt/by-name/<name>.xml`, where ``<name>`` is
full name of the domain. Wildcards are not supported but symlinks are.
Jinja has a concept of template names, which basically is the path below some
load point, which in Qubes' case is :file:`/etc/qubes/templates` and
:file:`/usr/share/qubes/templates`. Thus names of those templates are
respectively ``'libvirt/xen.xml'``, ``'libvirt/xen-dist.xml'``,
``'libvirt/xen-user.xml'`` and ``'libvirt/by-name/<name>.xml'``.
This will be important later.
.. note::
Those who know jinja python API will know that the abovementioned locations
aren't the only possibilities. Yes, it's a lie, but a justified one.
What to put in the template
---------------------------
In principle the user may put anything in the template and there is no attempt
to constrain the user from doing stupid things. One obvious thing is to copy the
original config file and make changes.
.. code-block:: jinja
<domain type="xen">
<name>{{ vm.name }}</name>
...
The better way is to inherit from the original template and override any number
of blocks. This is the point when we need the name of the original template.
.. code-block:: jinja
{% extends 'libvirt/xen.xml' %}
{% block devices %}
{{ super() }}
<serial type='pty'>
<target port='0'/>
</serial>
{% endblock %}
``{% extends %}`` specifies which template we inherit from. Then you may put any
block by putting new content inside ``{% block %}{% endblock %}``.
``{{ super() }}`` is substituted with original content of the block as specified
in the parent template. Untouched blocks remain as they were.
The example above adds serial device.
Template API
------------
.. warning::
This API is provisional and subject to change at the minor releases until
further notice. No backwards compatibility is promised.
Globals
```````
vm
the domain object (instance of subclass of
:py:class:`qubes.vm.qubesvm.QubesVM`)
Filters
```````
No custom filters at the moment.
Blocks in the default template
``````````````````````````````
basic
Contains ``<name>``, ``<uuid>``, ``<memory>``, ``<currentMemory>`` and
``<vcpu>`` nodes.
os
Contents of ``<os>`` node.
features
Contents of ``<features>`` node.
clock
Contains the ``<clock>`` node.
on
Contains ``<on_*>`` nodes.
devices
Contents of ``<devices>`` node.
.. vim: ts=3 sts=3 sw=3 et

Ver fichero

@ -625,7 +625,10 @@ class Qubes(qubes.PropertyHolder):
#: jinja2 environment for libvirt XML templates
self.env = jinja2.Environment(
loader=jinja2.FileSystemLoader('/usr/share/qubes/templates'),
loader=jinja2.FileSystemLoader([
'/etc/qubes/templates',
'/usr/share/qubes/templates',
]),
undefined=jinja2.StrictUndefined)
if load:

Ver fichero

@ -269,8 +269,12 @@ class BaseVM(qubes.PropertyHolder):
:param bool prepare_dvm: If we are in the process of preparing \
DisposableVM
'''
domain_config = self.app.env.get_template('libvirt/xen.xml').render(
vm=self, prepare_dvm=prepare_dvm)
domain_config = self.app.env.select_template([
'libvirt/xen/by-name/{}.xml'.format(self.name),
'libvirt/xen-user.xml',
'libvirt/xen-dist.xml',
'libvirt/xen.xml',
]).render(vm=self, prepare_dvm=prepare_dvm)
return domain_config

Ver fichero

@ -1,122 +1,134 @@
<domain type="xen">
<name>{% if prepare_dvm %}%NAME%{% else %}{{ vm.name }}{% endif %}</name>
<uuid>{{ vm.uuid }}</uuid>
<memory unit="MiB">{{ vm.maxmem }}</memory>
<currentMemory unit="MiB">{{ vm.memory }}</currentMemory>
<vcpu placement="static">{{ vm.vcpus }}</vcpu>
{% block basic %}
<name>{% if prepare_dvm %}%NAME%{% else %}{{ vm.name }}{% endif %}</name>
<uuid>{{ vm.uuid }}</uuid>
<memory unit="MiB">{{ vm.maxmem }}</memory>
<currentMemory unit="MiB">{{ vm.memory }}</currentMemory>
<vcpu placement="static">{{ vm.vcpus }}</vcpu>
{% endblock %}
<os>
{% if vm.hvm %}
<type arch="x86_64" machine="xenfv">hvm</type>
<loader>hvmloader</loader>
<boot dev="cdrom" />
<boot dev="hd" />
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
{% else %}
<type arch="x86_64" machine="xenpv">linux</type>
<kernel>{{ vm.storage.kernels_dir }}/vmlinuz</kernel>
<initrd>{{ vm.storage.kernels_dir }}/initramfs</initrd>
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH 3 {{ vm.kernelopts }}</cmdline>
{% endif %}
{% block os %}
{% if vm.hvm %}
<type arch="x86_64" machine="xenfv">hvm</type>
<loader>hvmloader</loader>
<boot dev="cdrom" />
<boot dev="hd" />
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
{% else %}
<type arch="x86_64" machine="xenpv">linux</type>
<kernel>{{ vm.storage.kernels_dir }}/vmlinuz</kernel>
<initrd>{{ vm.storage.kernels_dir }}/initramfs</initrd>
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH 3 {{ vm.kernelopts }}</cmdline>
{% endif %}
{% endblock %}
</os>
<features>
{% if vm.hvm %}
<pae/>
<acpi/>
<apic/>
<viridian/>
{% endif %}
{% block features %}
{% if vm.hvm %}
<pae/>
<acpi/>
<apic/>
<viridian/>
{% endif %}
{% if vm.devices['pci'].attached(persistent=True) | list
and vm.features.get('pci-e820-host', True) %}
<xen>
<e820_host state="on"/>
</xen>
{% endif %}
{% if vm.devices['pci'].attached(persistent=True) | list
and vm.features.get('pci-e820-host', True) %}
<xen>
<e820_host state="on"/>
</xen>
{% endif %}
{% endblock %}
</features>
{% if vm.hvm %}
{% set timezone = vm.features.check_with_template('timezone', 'localtime').lower() %}
{% if timezone == 'localtime' %}
<clock offset="variable" adjustment="0" basis="localtime" />
{% elif timezone.isdigit() %}
<clock offset="variable" adjustment="{{ timezone }}" basis="UTC" />
{% else %}
<clock offset="variable" adjustment="0" basis="UTC" />
{% endif %}
{% else %}
<clock offset='utc' adjustment='reset'>
<timer name="tsc" mode="native"/>
</clock>
{% endif %}
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
<devices>
{% set i = 0 %}
{# TODO Allow more volumes out of the box #}
{% set dd = ['e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
%}
{% for device in vm.block_devices %}
<disk type="block" device="{{ device.devtype }}">
<driver name="phy" />
<source dev="{{ device.path }}" />
{% if device.name == 'root' %}
<target dev="xvda" />
{% elif device.name == 'private' %}
<target dev="xvdb" />
{% elif device.name == 'volatile' %}
<target dev="xvdc" />
{% elif device.name == 'kernel' %}
<target dev="xvdd" />
{% else %}
<target dev="xvd{{dd[i]}}" />
{% set i = i + 1 %}
{% endif %}
{% if not device.rw %}
<readonly />
{% endif %}
{% if device.domain %}
<backenddomain name="{{ domain }}" />
{% endif %}
{% if device.script %}
<script path="{{ device.script }}" />
{% endif %}
</disk>
{% endfor %}
{% if vm.netvm %}
{% include 'libvirt/devices/net.xml' with context %}
{% endif %}
{% for device in vm.devices.pci.attached(persistent=True) %}
{% include 'libvirt/devices/pci.xml' %}
{% endfor %}
{% block clock %}
{% if vm.hvm %}
<emulator
type="stubdom"
{% if vm.netvm %}
cmdline="-net lwip,client_ip={{ vm.ip -}}
,server_ip={{ vm.secondary_dns -}}
,dns={{ vm.netvm.gateway -}}
,gw={{ self.netvm.gateway -}}
,netmask={{ vm.netmask }}"
{% endif %}
/>
<input type="tablet" bus="usb"/>
<video type="vga"/>
{% set timezone = vm.features.check_with_template('timezone', 'localtime').lower() %}
{% if timezone == 'localtime' %}
<clock offset="variable" adjustment="0" basis="localtime" />
{% elif timezone.isdigit() %}
<clock offset="variable" adjustment="{{ timezone }}" basis="UTC" />
{% else %}
<clock offset="variable" adjustment="0" basis="UTC" />
{% endif %}
{% else %}
<console type="pty">
<target type="xen" port="0"/>
</console>
<clock offset='utc' adjustment='reset'>
<timer name="tsc" mode="native"/>
</clock>
{% endif %}
{% endblock %}
{% block on %}
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
{% endblock %}
<devices>
{% block devices %}
{% set i = 0 %}
{# TODO Allow more volumes out of the box #}
{% set dd = ['e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
%}
{% for device in vm.block_devices %}
<disk type="block" device="{{ device.devtype }}">
<driver name="phy" />
<source dev="{{ device.path }}" />
{% if device.name == 'root' %}
<target dev="xvda" />
{% elif device.name == 'private' %}
<target dev="xvdb" />
{% elif device.name == 'volatile' %}
<target dev="xvdc" />
{% elif device.name == 'kernel' %}
<target dev="xvdd" />
{% else %}
<target dev="xvd{{dd[i]}}" />
{% set i = i + 1 %}
{% endif %}
{% if not device.rw %}
<readonly />
{% endif %}
{% if device.domain %}
<backenddomain name="{{ domain }}" />
{% endif %}
{% if device.script %}
<script path="{{ device.script }}" />
{% endif %}
</disk>
{% endfor %}
{% if vm.netvm %}
{% include 'libvirt/devices/net.xml' with context %}
{% endif %}
{% for device in vm.devices.pci.attached(persistent=True) %}
{% include 'libvirt/devices/pci.xml' %}
{% endfor %}
{% if vm.hvm %}
<emulator
type="stubdom"
{% if vm.netvm %}
cmdline="-net lwip,client_ip={{ vm.ip -}}
,server_ip={{ vm.secondary_dns -}}
,dns={{ vm.netvm.gateway -}}
,gw={{ self.netvm.gateway -}}
,netmask={{ vm.netmask }}"
{% endif %}
/>
<input type="tablet" bus="usb"/>
<video type="vga"/>
{% else %}
<console type="pty">
<target type="xen" port="0"/>
</console>
{% endif %}
{% endblock %}
</devices>
</domain>