Просмотр исходного кода

Add possibility to override libvirt config

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

fixes QubesOS/qubes-issues#1798
Wojtek Porczyk 7 лет назад
Родитель
Сommit
9dc37c1ee7
5 измененных файлов с 238 добавлено и 104 удалено
  1. 1 0
      doc/index.rst
  2. 114 0
      doc/libvirt.rst
  3. 4 1
      qubes/app.py
  4. 6 2
      qubes/vm/__init__.py
  5. 113 101
      templates/libvirt/xen.xml

+ 1 - 0
doc/index.rst

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

+ 114 - 0
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/<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

+ 4 - 1
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:

+ 6 - 2
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
 
 

+ 113 - 101
templates/libvirt/xen.xml

@@ -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" />
+    {% block clock %}
+        {% 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="variable" adjustment="0" basis="UTC" />
+            <clock offset='utc' adjustment='reset'>
+                <timer name="tsc" mode="native"/>
+            </clock>
         {% endif %}
-    {% else %}
-        <clock offset='utc' adjustment='reset'>
-            <timer name="tsc" mode="native"/>
-        </clock>
-    {% endif %}
+    {% endblock %}
 
-    <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 %}
+    {% block on %}
+        <on_poweroff>destroy</on_poweroff>
+        <on_reboot>destroy</on_reboot>
+        <on_crash>destroy</on_crash>
+    {% endblock %}
 
-                {% if not device.rw %}
-                    <readonly />
-                {% endif %}
+    <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 device.domain %}
-                    <backenddomain name="{{ domain }}" />
-                {% endif %}
+                    {% if not device.rw %}
+                        <readonly />
+                    {% endif %}
 
-                {% if device.script %}
-                    <script path="{{ device.script }}" />
-                {% endif %}
-            </disk>
-        {% endfor %}
+                    {% if device.domain %}
+                        <backenddomain name="{{ domain }}" />
+                    {% endif %}
 
-        {% if vm.netvm %}
-            {% include 'libvirt/devices/net.xml' with context %}
-        {% endif %}
+                    {% if device.script %}
+                        <script path="{{ device.script }}" />
+                    {% endif %}
+                </disk>
+            {% endfor %}
 
-        {% for device in vm.devices.pci.attached(persistent=True) %}
-            {% include 'libvirt/devices/pci.xml' %}
-        {% endfor %}
+            {% if vm.netvm %}
+                {% include 'libvirt/devices/net.xml' with context %}
+            {% endif %}
 
-        {% 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 %}
+            {% 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>