From 9dc37c1ee7feaf72fc90d4eb1677ea1ca2275b7f Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Tue, 4 Oct 2016 11:30:29 +0200 Subject: [PATCH] Add possibility to override libvirt config This is the equivalent of "custom config" from R3.x. fixes QubesOS/qubes-issues#1798 --- doc/index.rst | 1 + doc/libvirt.rst | 114 +++++++++++++++++++ qubes/app.py | 5 +- qubes/vm/__init__.py | 8 +- templates/libvirt/xen.xml | 226 ++++++++++++++++++++------------------ 5 files changed, 244 insertions(+), 110 deletions(-) create mode 100644 doc/libvirt.rst diff --git a/doc/index.rst b/doc/index.rst index ee17675e..bfde6ce6 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -26,6 +26,7 @@ manpages and API documentation. For primary user documentation, see .. toctree:: :maxdepth: 1 + libvirt autoxml manpages/index diff --git a/doc/libvirt.rst b/doc/libvirt.rst new file mode 100644 index 00000000..bd35fe49 --- /dev/null +++ b/doc/libvirt.rst @@ -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/.xml`, where ```` 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/.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 + + + {{ vm.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() }} + + + + {% 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 ````, ````, ````, ```` and + ```` nodes. + +os + Contents of ```` node. + +features + Contents of ```` node. + +clock + Contains the ```` node. + +on + Contains ```` nodes. + +devices + Contents of ```` node. + + +.. vim: ts=3 sts=3 sw=3 et diff --git a/qubes/app.py b/qubes/app.py index 088134f0..ba23512b 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -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: diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index 66542b57..54984866 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -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 diff --git a/templates/libvirt/xen.xml b/templates/libvirt/xen.xml index 147a8c9b..2e93711e 100644 --- a/templates/libvirt/xen.xml +++ b/templates/libvirt/xen.xml @@ -1,122 +1,134 @@ -{% if prepare_dvm %}%NAME%{% else %}{{ vm.name }}{% endif %} - {{ vm.uuid }} - {{ vm.maxmem }} - {{ vm.memory }} - {{ vm.vcpus }} + {% block basic %} + {% if prepare_dvm %}%NAME%{% else %}{{ vm.name }}{% endif %} + {{ vm.uuid }} + {{ vm.maxmem }} + {{ vm.memory }} + {{ vm.vcpus }} + {% endblock %} - {% if vm.hvm %} - hvm - hvmloader - - - - {% else %} - linux - {{ vm.storage.kernels_dir }}/vmlinuz - {{ vm.storage.kernels_dir }}/initramfs - root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH 3 {{ vm.kernelopts }} - {% endif %} + {% block os %} + {% if vm.hvm %} + hvm + hvmloader + + + + {% else %} + linux + {{ vm.storage.kernels_dir }}/vmlinuz + {{ vm.storage.kernels_dir }}/initramfs + root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH 3 {{ vm.kernelopts }} + {% endif %} + {% endblock %} - {% if vm.hvm %} - - - - - {% endif %} + {% block features %} + {% if vm.hvm %} + + + + + {% endif %} - {% if vm.devices['pci'].attached(persistent=True) | list - and vm.features.get('pci-e820-host', True) %} - - - - {% endif %} + {% if vm.devices['pci'].attached(persistent=True) | list + and vm.features.get('pci-e820-host', True) %} + + + + {% endif %} + {% endblock %} - {% if vm.hvm %} - {% set timezone = vm.features.check_with_template('timezone', 'localtime').lower() %} - {% if timezone == 'localtime' %} - - {% elif timezone.isdigit() %} - - {% else %} - - {% endif %} - {% else %} - - - - {% endif %} - - destroy - destroy - destroy - - {% 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 %} - - - - {% if device.name == 'root' %} - - {% elif device.name == 'private' %} - - {% elif device.name == 'volatile' %} - - {% elif device.name == 'kernel' %} - - {% else %} - - {% set i = i + 1 %} - {% endif %} - - {% if not device.rw %} - - {% endif %} - - {% if device.domain %} - - {% endif %} - - {% if device.script %} -