HVM part 1

This commit is contained in:
Wojtek Porczyk 2016-03-02 12:17:29 +01:00
parent e0210130da
commit 5eaf03c4a2
15 changed files with 261 additions and 195 deletions

View File

@ -80,8 +80,8 @@ endif
cp qubes-rpc/qubes-notify-tools $(DESTDIR)/usr/libexec/qubes/ cp qubes-rpc/qubes-notify-tools $(DESTDIR)/usr/libexec/qubes/
mkdir -p "$(DESTDIR)$(FILESDIR)" mkdir -p "$(DESTDIR)$(FILESDIR)"
cp vm-config/$(BACKEND_VMM)-vm-template.xml "$(DESTDIR)$(FILESDIR)/vm-template.xml" cp -r templates "$(DESTDIR)$(FILESDIR)/templates"
cp vm-config/$(BACKEND_VMM)-vm-template-hvm.xml "$(DESTDIR)$(FILESDIR)/vm-template-hvm.xml" rm -f "$(DESTDIR)$(FILESDIR)/templates/README"
mkdir -p $(DESTDIR)$(DATADIR) mkdir -p $(DESTDIR)$(DATADIR)
mkdir -p $(DESTDIR)$(DATADIR)/vm-templates mkdir -p $(DESTDIR)$(DATADIR)/vm-templates

View File

@ -53,6 +53,7 @@ import __builtin__
import docutils.core import docutils.core
import docutils.io import docutils.io
import jinja2
import lxml.etree import lxml.etree
import pkg_resources import pkg_resources
@ -1199,6 +1200,11 @@ class Qubes(PropertyHolder):
self.__load_timestamp = None self.__load_timestamp = None
#: jinja2 environment for libvirt XML templates
self.env = jinja2.Environment(
loader=jinja2.FileSystemLoader('/usr/share/qubes/templates'),
undefined=jinja2.StrictUndefined)
if load: if load:
self.load() self.load()

115
qubes/devices.py Normal file
View File

@ -0,0 +1,115 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2010-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2015-2016 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
import re
class DeviceCollection(object):
'''Bag for devices.
Used as default value for :py:meth:`DeviceManager.__missing__` factory.
:param vm: VM for which we manage devices
:param class_: device class
'''
def __init__(self, vm, class_):
self._vm = vm
self._class = class_
self._set = set()
def attach(self, device):
'''Attach (add) device to domain.
:param str device: device identifier (format is class-dependent)
'''
if device in self:
raise KeyError(
'device {!r} of class {} already attached to {!r}'.format(
device, self._class, self._vm))
self._vm.fire_event_pre('device-pre-attach:{}'.format(self._class),
device)
self._set.add(device)
self._vm.fire_event('device-attach:{}'.format(self._class), device)
def detach(self, device):
'''Detach (remove) device from domain.
:param str device: device identifier (format is class-dependent)
'''
if device not in self:
raise KeyError(
'device {!r} of class {} not attached to {!r}'.format(
device, self._class, self._vm))
self._vm.fire_event_pre('device-pre-detach:{}'.format(self._class),
device)
self._set.remove(device)
self._vm.fire_event('device-detach:{}'.format(self._class), device)
def __iter__(self):
return iter(self._set)
def __contains__(self, item):
return item in self._set
def __len__(self):
return len(self._set)
class DeviceManager(dict):
'''Device manager that hold all devices by their classess.
:param vm: VM for which we manage devices
'''
def __init__(self, vm):
super(DeviceManager, self).__init__()
self._vm = vm
def __missing__(self, key):
self[key] = DeviceCollection(self._vm, key)
return self[key]
class RegexDevice(str):
def __init__(self, *args, **kwargs):
super(RegexDevice, self).__init__(*args, **kwargs)
dev_match = self.regex.match(self)
if not dev_match:
raise ValueError('Invalid device identifier: {!r}'.format(self))
for group in self.regex.groupindex:
setattr(self, group, dev_match.group(group))
class PCIDevice(RegexDevice):
regex = re.compile(
r'^(?P<bus>[0-9a-f]+):(?P<device>[0-9a-f]+)\.(?P<function>[0-9a-f]+)$')

View File

@ -291,7 +291,6 @@ class StatusColumn(Column):
import qubes.vm.adminvm import qubes.vm.adminvm
import qubes.vm.appvm import qubes.vm.appvm
import qubes.vm.dispvm import qubes.vm.dispvm
import qubes.vm.hvm
import qubes.vm.qubesvm import qubes.vm.qubesvm
import qubes.vm.templatevm import qubes.vm.templatevm
@ -310,7 +309,7 @@ class StatusColumn(Column):
ret = 'd' ret = 'd'
if ret is not None: if ret is not None:
if isinstance(vm, qubes.vm.hvm.HVM): if getattr(vm, 'hvm', False):
return ret.upper() return ret.upper()
else: else:
return ret return ret

View File

@ -43,6 +43,7 @@ import lxml.etree
import qubes import qubes
import qubes.log import qubes.log
import qubes.devices
import qubes.events import qubes.events
import qubes.tools.qvm_ls import qubes.tools.qvm_ls
@ -145,80 +146,6 @@ class BaseVMMeta(qubes.events.EmitterMeta):
qubes.tools.qvm_ls.process_class(cls) qubes.tools.qvm_ls.process_class(cls)
class DeviceCollection(object):
'''Bag for devices.
Used as default value for :py:meth:`DeviceManager.__missing__` factory.
:param vm: VM for which we manage devices
:param class_: device class
'''
def __init__(self, vm, class_):
self._vm = vm
self._class = class_
self._set = set()
def attach(self, device):
'''Attach (add) device to domain.
:param str device: device identifier (format is class-dependent)
'''
if device in self:
raise KeyError(
'device {!r} of class {} already attached to {!r}'.format(
device, self._class, self._vm))
self._vm.fire_event_pre('device-pre-attached:{}'.format(self._class),
device)
self._set.add(device)
self._vm.fire_event('device-attached:{}'.format(self._class), device)
def detach(self, device):
'''Detach (remove) device from domain.
:param str device: device identifier (format is class-dependent)
'''
if device not in self:
raise KeyError(
'device {!r} of class {} not attached to {!r}'.format(
device, self._class, self._vm))
self._vm.fire_event_pre('device-pre-detached:{}'.format(self._class),
device)
self._set.remove(device)
self._vm.fire_event('device-detached:{}'.format(self._class), device)
def __iter__(self):
return iter(self._set)
def __contains__(self, item):
return item in self._set
def __len__(self):
return len(self._set)
class DeviceManager(dict):
'''Device manager that hold all devices by their classess.
:param vm: VM for which we manage devices
'''
def __init__(self, vm):
super(DeviceManager, self).__init__()
self._vm = vm
def __missing__(self, key):
self[key] = DeviceCollection(self._vm, key)
return self[key]
class BaseVM(qubes.PropertyHolder): class BaseVM(qubes.PropertyHolder):
'''Base class for all VMs '''Base class for all VMs
@ -251,7 +178,7 @@ class BaseVM(qubes.PropertyHolder):
#: :py:class:`DeviceManager` object keeping devices that are attached to #: :py:class:`DeviceManager` object keeping devices that are attached to
#: this domain #: this domain
self.devices = DeviceManager(self) if devices is None else devices self.devices = devices or qubes.devices.DeviceManager(self)
#: user-specified tags #: user-specified tags
self.tags = tags or {} self.tags = tags or {}
@ -480,13 +407,8 @@ class BaseVM(qubes.PropertyHolder):
conf_template = f_conf_template.read() conf_template = f_conf_template.read()
f_conf_template.close() f_conf_template.close()
template_params = self.get_config_params() domain_config = self.app.env.get_template('libvirt/xen.xml').render(
if prepare_dvm: vm=self, prepare_dvm=prepare_dvm)
template_params['name'] = '%NAME%'
template_params['privatedev'] = ''
template_params['netdev'] = re.sub(r"address='[0-9.]*'",
"address='%IP%'", template_params['netdev'])
domain_config = conf_template.format(**template_params)
# FIXME: This is only for debugging purposes # FIXME: This is only for debugging purposes
old_umask = os.umask(002) old_umask = os.umask(002)

View File

@ -1,9 +0,0 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
import qubes.vm.qubesvm
class HVM(qubes.vm.qubesvm.QubesVM):
'''HVM'''
def __init__(self, D):
super(HVM, self).__init__(D)

View File

@ -166,6 +166,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
ls_width=36, ls_width=36,
doc='UUID from libvirt.') doc='UUID from libvirt.')
hvm = qubes.property('hvm',
type=bool, setter=qubes.property.bool,
default=False,
doc='''Use full virtualisation (HVM) for this qube,
instead of paravirtualisation (PV)''')
# XXX this should be part of qubes.xml # XXX this should be part of qubes.xml
firewall_conf = qubes.property('firewall_conf', type=str, firewall_conf = qubes.property('firewall_conf', type=str,
default='firewall.xml') default='firewall.xml')

View File

@ -1,9 +0,0 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
import qubes.vm.hvm
class TemplateHVM(qubes.vm.hvm.HVM):
'''Template for HVM'''
def __init__(self, D):
super(TemplateHVM, self).__init__(D)

View File

@ -202,6 +202,7 @@ fi
%dir %{python_sitelib}/qubes %dir %{python_sitelib}/qubes
%{python_sitelib}/qubes/__init__.py* %{python_sitelib}/qubes/__init__.py*
%{python_sitelib}/qubes/config.py* %{python_sitelib}/qubes/config.py*
%{python_sitelib}/qubes/devices.py*
%{python_sitelib}/qubes/dochelpers.py* %{python_sitelib}/qubes/dochelpers.py*
%{python_sitelib}/qubes/events.py* %{python_sitelib}/qubes/events.py*
%{python_sitelib}/qubes/exc.py* %{python_sitelib}/qubes/exc.py*
@ -214,9 +215,7 @@ fi
%{python_sitelib}/qubes/vm/adminvm.py* %{python_sitelib}/qubes/vm/adminvm.py*
%{python_sitelib}/qubes/vm/appvm.py* %{python_sitelib}/qubes/vm/appvm.py*
%{python_sitelib}/qubes/vm/dispvm.py* %{python_sitelib}/qubes/vm/dispvm.py*
%{python_sitelib}/qubes/vm/hvm.py*
%{python_sitelib}/qubes/vm/qubesvm.py* %{python_sitelib}/qubes/vm/qubesvm.py*
%{python_sitelib}/qubes/vm/templatehvm.py*
%{python_sitelib}/qubes/vm/templatevm.py* %{python_sitelib}/qubes/vm/templatevm.py*
%dir %{python_sitelib}/qubes/vm/mix %dir %{python_sitelib}/qubes/vm/mix
@ -314,8 +313,7 @@ fi
%attr(2770,root,qubes) %dir /var/lib/qubes/backup %attr(2770,root,qubes) %dir /var/lib/qubes/backup
%attr(2770,root,qubes) %dir /var/lib/qubes/dvmdata %attr(2770,root,qubes) %dir /var/lib/qubes/dvmdata
%attr(2770,root,qubes) %dir /var/lib/qubes/vm-kernels %attr(2770,root,qubes) %dir /var/lib/qubes/vm-kernels
/usr/share/qubes/vm-template.xml /usr/share/qubes/templates/libvirt/xen.xml
/usr/share/qubes/vm-template-hvm.xml
/usr/lib/tmpfiles.d/qubes.conf /usr/lib/tmpfiles.d/qubes.conf
/usr/lib/qubes/qubes-prepare-saved-domain.sh /usr/lib/qubes/qubes-prepare-saved-domain.sh
/usr/lib/qubes/qubes-update-dispvm-savefile-with-progress.sh /usr/lib/qubes/qubes-update-dispvm-savefile-with-progress.sh

4
templates/README Normal file
View File

@ -0,0 +1,4 @@
Here were files with config for WNI, available in repo's history.
vm-config/wni-vm-template-hvm.xml
vm-config/wni-vm-template.xml

121
templates/libvirt/xen.xml Normal file
View File

@ -0,0 +1,121 @@
<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>
<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 %}
</os>
{% if vm.hvm %}
<features>
<pae/>
<acpi/>
<apic/>
<viridian/>
</features>
{# TODO
<clock offset="variable"
adjustment='{timeoffset}' basis='{time_basis}'/>
#}
{% 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>
{#
{% for device in vm.storage %}
<disk type="block" device="{{ device.type }}">
<driver name="phy" />
<source dev="{{ device.path }}" />
<target dev="{{ device.vdev }}" />
{% if not device.rw %}
<readonly />
{% endif %}
{% if device.domain %}
<domain name="{{ domain }}" />
{% endif %}
{% if device.script %}
<script path="{{ device.script }}"></script>
{% endif %}
</disk>
{% endfor %}
#}
{{ vm.storage.root_dev_config() }}
{% if not prepare_dvm %}{{ vm.storage.private_dev_config() }}{% endif %}
{{ vm.storage.other_dev_config() }}
{% if not vm.hvm %}
{{ vm.storage.volatile_dev_config() }}
{% endif %}
{% if vm.netvm %}
<interface type="ethernet">
<mac address="{{ vm.mac }}" />
<ip address="
{%- if prepare_dvm -%}
%IP%
{%- else -%}
{{ vm.ip }}
{%- endif %}" />
<backenddomain name="{{ vm.netvm.name }}" />
<script path="vif-route-qubes"></script>
</interface>
{% endif %}
{% for device in vm.devices.pci %}
<hostdev type="pci" managed="yes">
<source>
<address
bus="0x{{ device.bus }}"
slot="0x{{ device.device }}"
function="0x{{ device.function }}" />
</source>
</hostdev>
{% 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 %}
</devices>
</domain>
<!-- vim: set ft=jinja ts=4 sts=4 sw=4 et tw=80 : -->

View File

@ -1,9 +0,0 @@
<domain type='test'>
<name>{name}</name>
<uuid>{uuid}</uuid>
<memory unit='MiB'>{maxmem}</memory>
<os>
<type>hvm</type>
</os>
</domain>

View File

@ -1,9 +0,0 @@
<domain type='test'>
<name>{name}</name>
<uuid>{uuid}</uuid>
<memory unit='MiB'>{maxmem}</memory>
<os>
<type>pv</type>
</os>
</domain>

View File

@ -1,38 +0,0 @@
<domain type='xen'>
<name>{name}</name>
<uuid>{uuid}</uuid>
<memory unit='MiB'>{maxmem}</memory>
<currentMemory unit='MiB'>{mem}</currentMemory>
<vcpu placement='static'>{vcpus}</vcpu>
<os>
<type arch='x86_64' machine='xenfv'>hvm</type>
<loader>hvmloader</loader>
<boot dev='cdrom'/>
<boot dev='hd'/>
</os>
<features>
<pae/>
<acpi/>
<apic/>
<viridian/>
</features>
<clock offset='variable' adjustment='{timeoffset}' basis='{time_basis}'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
<devices>
{no_network_begin}<emulator type='stubdom'/>{no_network_end}
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
{network_begin}<emulator type='stubdom' cmdline='-net lwip,client_ip={ip},server_ip={dns2},dns={dns1},gw={gateway},netmask={netmask}'/>{network_end}
{rootdev}
{privatedev}
{otherdevs}
{netdev}
{pcidevs}
<input type='tablet' bus='usb'/>
<video type='vga'>
<model type='xen' vram='16384'/>
</video>
</devices>
</domain>

View File

@ -1,31 +0,0 @@
<domain type='xen'>
<name>{name}</name>
<uuid>{uuid}</uuid>
<memory unit='MiB'>{maxmem}</memory>
<currentMemory unit='MiB'>{mem}</currentMemory>
<vcpu placement='static'>{vcpus}</vcpu>
<os>
<type arch='x86_64' machine='xenpv'>linux</type>
<kernel>{kerneldir}/vmlinuz</kernel>
<initrd>{kerneldir}/initramfs</initrd>
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH 3 {kernelopts}</cmdline>
</os>
<clock offset='utc' adjustment='reset'>
<timer name="tsc" mode="native"/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>destroy</on_reboot>
<on_crash>destroy</on_crash>
<devices>
{rootdev}
{privatedev}
{volatiledev}
{otherdevs}
{netdev}
{pcidevs}
<console type='pty'>
<target type='xen' port='0'/>
</console>
</devices>
</domain>