core3 move: QubesVM
This is a big commit and probably incomplete. Tests will follow.
This commit is contained in:
parent
6b7860995b
commit
41fef46db2
@ -33,6 +33,7 @@ extensions = [
|
|||||||
'sphinx.ext.autosummary',
|
'sphinx.ext.autosummary',
|
||||||
'sphinx.ext.coverage',
|
'sphinx.ext.coverage',
|
||||||
'sphinx.ext.doctest',
|
'sphinx.ext.doctest',
|
||||||
|
'sphinx.ext.graphviz',
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
'sphinx.ext.todo',
|
'sphinx.ext.todo',
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
@ -180,6 +181,9 @@ html_last_updated_fmt = '%d.%m.%Y'
|
|||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
#html_file_suffix = None
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# html links do not work with svg!
|
||||||
|
graphviz_output_format = 'png'
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'core-admin-doc'
|
htmlhelp_basename = 'core-admin-doc'
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class QubesException(Exception):
|
|||||||
'''Exception that can be shown to the user'''
|
'''Exception that can be shown to the user'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class QubesVMMConnection(object):
|
class VMMConnection(object):
|
||||||
'''Connection to Virtual Machine Manager (libvirt)'''
|
'''Connection to Virtual Machine Manager (libvirt)'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._libvirt_conn = None
|
self._libvirt_conn = None
|
||||||
@ -118,13 +118,18 @@ class QubesVMMConnection(object):
|
|||||||
self.init_vmm_connection()
|
self.init_vmm_connection()
|
||||||
return self._xs
|
return self._xs
|
||||||
|
|
||||||
vmm = QubesVMMConnection()
|
|
||||||
|
|
||||||
|
|
||||||
class QubesHost(object):
|
class QubesHost(object):
|
||||||
'''Basic information about host machine'''
|
'''Basic information about host machine
|
||||||
def __init__(self):
|
|
||||||
(model, memory, cpus, mhz, nodes, socket, cores, threads) = vmm.libvirt_conn.getInfo()
|
:param Qubes app: Qubes application context (must have :py:attr:`Qubes.vmm` attribute defined)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self._app = app
|
||||||
|
|
||||||
|
(model, memory, cpus, mhz, nodes, socket, cores, threads) = \
|
||||||
|
self._app.vmm.libvirt_conn.getInfo()
|
||||||
self._total_mem = long(memory)*1024
|
self._total_mem = long(memory)*1024
|
||||||
self._no_cpus = cpus
|
self._no_cpus = cpus
|
||||||
|
|
||||||
@ -154,7 +159,7 @@ class QubesHost(object):
|
|||||||
if previous is None:
|
if previous is None:
|
||||||
previous_time = time.time()
|
previous_time = time.time()
|
||||||
previous = {}
|
previous = {}
|
||||||
info = vmm.xc.domain_getinfo(0, qubes_max_qid)
|
info = self._app.vmm.xc.domain_getinfo(0, qubes_max_qid)
|
||||||
for vm in info:
|
for vm in info:
|
||||||
previous[vm['domid']] = {}
|
previous[vm['domid']] = {}
|
||||||
previous[vm['domid']]['cpu_time'] = (
|
previous[vm['domid']]['cpu_time'] = (
|
||||||
@ -164,7 +169,7 @@ class QubesHost(object):
|
|||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
current = {}
|
current = {}
|
||||||
info = vmm.xc.domain_getinfo(0, qubes_max_qid)
|
info = self._app.vmm.xc.domain_getinfo(0, qubes_max_qid)
|
||||||
for vm in info:
|
for vm in info:
|
||||||
current[vm['domid']] = {}
|
current[vm['domid']] = {}
|
||||||
current[vm['domid']]['cpu_time'] = (
|
current[vm['domid']]['cpu_time'] = (
|
||||||
@ -427,18 +432,38 @@ class property(object):
|
|||||||
|
|
||||||
:param str name: name of the property
|
:param str name: name of the property
|
||||||
:param collections.Callable setter: if not :py:obj:`None`, this is used to initialise value; first parameter to the function is holder instance and the second is value; this is called before ``type``
|
:param collections.Callable setter: if not :py:obj:`None`, this is used to initialise value; first parameter to the function is holder instance and the second is value; this is called before ``type``
|
||||||
|
:param collections.Callable saver: function to coerce value to something readable by setter
|
||||||
:param type type: if not :py:obj:`None`, value is coerced to this type
|
:param type type: if not :py:obj:`None`, value is coerced to this type
|
||||||
:param object default: default value
|
:param object default: default value; if callable, will be called with holder as first argument
|
||||||
:param int load_stage: stage when property should be loaded (see :py:class:`Qubes` for description of stages)
|
:param int load_stage: stage when property should be loaded (see :py:class:`Qubes` for description of stages)
|
||||||
:param int order: order of evaluation (bigger order values are later)
|
:param int order: order of evaluation (bigger order values are later)
|
||||||
:param str doc: docstring; you may use RST markup
|
:param str doc: docstring; you may use RST markup
|
||||||
|
|
||||||
|
Setters and savers have following signatures:
|
||||||
|
|
||||||
|
.. :py:function:: setter(self, prop, value)
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
:param self: instance of object that is holding property
|
||||||
|
:param prop: property object
|
||||||
|
:param value: value being assigned
|
||||||
|
|
||||||
|
.. :py:function:: saver(self, prop, value)
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
:param self: instance of object that is holding property
|
||||||
|
:param prop: property object
|
||||||
|
:param value: value being saved
|
||||||
|
:rtype: str
|
||||||
|
:raises property.DontSave: when property should not be saved at all
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, name, setter=None, type=None, default=None,
|
def __init__(self, name, setter=None, saver=None, type=None, default=None,
|
||||||
load_stage=2, order=0, save_via_ref=False, doc=None):
|
load_stage=2, order=0, save_via_ref=False, doc=None):
|
||||||
self.__name__ = name
|
self.__name__ = name
|
||||||
self._setter = setter
|
self._setter = setter
|
||||||
|
self._saver = saver if saver is not None else (lambda self, prop, value: str(value))
|
||||||
self._type = type
|
self._type = type
|
||||||
self._default = default
|
self._default = default
|
||||||
self.order = order
|
self.order = order
|
||||||
@ -484,6 +509,12 @@ class property(object):
|
|||||||
if self._type is not None:
|
if self._type is not None:
|
||||||
value = self._type(value)
|
value = self._type(value)
|
||||||
|
|
||||||
|
if has_oldvalue:
|
||||||
|
instance.fire_event('property-pre-set:' + self.__name__, value, oldvalue)
|
||||||
|
else:
|
||||||
|
instance.fire_event('property-pre-set:' + self.__name__, value)
|
||||||
|
|
||||||
|
|
||||||
instance._init_property(self, value)
|
instance._init_property(self, value)
|
||||||
|
|
||||||
if has_oldvalue:
|
if has_oldvalue:
|
||||||
@ -509,13 +540,28 @@ class property(object):
|
|||||||
return self.__name__ == other.__name__
|
return self.__name__ == other.__name__
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# exceptions
|
||||||
|
#
|
||||||
|
|
||||||
|
class DontSave(Exception):
|
||||||
|
'''This exception may be raised from saver to sing that property should
|
||||||
|
not be saved.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def dontsave(self, prop, value):
|
||||||
|
'''Dummy saver that never saves anything.'''
|
||||||
|
raise DontSave()
|
||||||
|
|
||||||
#
|
#
|
||||||
# some setters provided
|
# some setters provided
|
||||||
#
|
#
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def forbidden(self, prop, value):
|
def forbidden(self, prop, value):
|
||||||
'''Property setter that forbids loading a property
|
'''Property setter that forbids loading a property.
|
||||||
|
|
||||||
This is used to effectively disable property in classes which inherit
|
This is used to effectively disable property in classes which inherit
|
||||||
unwanted property. When someone attempts to load such a property, it
|
unwanted property. When someone attempts to load such a property, it
|
||||||
@ -527,6 +573,22 @@ class property(object):
|
|||||||
prop.__name__, self.__class__.__name__))
|
prop.__name__, self.__class__.__name__))
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bool(self, prop, value):
|
||||||
|
'''Property setter for boolean properties.
|
||||||
|
|
||||||
|
It accepts (case-insensitive) ``'0'``, ``'no'`` and ``false`` as
|
||||||
|
:py:obj:`False` and ``'1'``, ``'yes'`` and ``'true'`` as
|
||||||
|
:py:obj:`True`.
|
||||||
|
'''
|
||||||
|
|
||||||
|
lcvalue = value.lower()
|
||||||
|
if lcvalue in ('0', 'no', 'false'): return False
|
||||||
|
if lcvalue in ('1', 'yes', 'true'): return True
|
||||||
|
raise ValueError('Invalid literal for boolean property: {!r}'.format(value))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PropertyHolder(qubes.events.Emitter):
|
class PropertyHolder(qubes.events.Emitter):
|
||||||
'''Abstract class for holding :py:class:`qubes.property`
|
'''Abstract class for holding :py:class:`qubes.property`
|
||||||
|
|
||||||
@ -539,8 +601,17 @@ class PropertyHolder(qubes.events.Emitter):
|
|||||||
|
|
||||||
.. event:: property-set:<propname> (subject, event, name, newvalue[, oldvalue])
|
.. event:: property-set:<propname> (subject, event, name, newvalue[, oldvalue])
|
||||||
|
|
||||||
Fired when property changes state. Signature is variable, *oldvalue* is
|
Fired when property changes state. Signature is variable,
|
||||||
present only if there was an old value.
|
*oldvalue* is present only if there was an old value.
|
||||||
|
|
||||||
|
:param name: Property name
|
||||||
|
:param newvalue: New value of the property
|
||||||
|
:param oldvalue: Old value of the property
|
||||||
|
|
||||||
|
.. event:: property-pre-set:<propname> (subject, event, name, newvalue[, oldvalue])
|
||||||
|
|
||||||
|
Fired before property changes state. Signature is variable,
|
||||||
|
*oldvalue* is present only if there was an old value.
|
||||||
|
|
||||||
:param name: Property name
|
:param name: Property name
|
||||||
:param newvalue: New value of the property
|
:param newvalue: New value of the property
|
||||||
@ -625,11 +696,16 @@ class PropertyHolder(qubes.events.Emitter):
|
|||||||
|
|
||||||
for prop in self.get_props_list():
|
for prop in self.get_props_list():
|
||||||
try:
|
try:
|
||||||
value = str(getattr(self, (prop.__name__ if with_defaults else prop._attr_name)))
|
value = getattr(self, (prop.__name__ if with_defaults else prop._attr_name))
|
||||||
except AttributeError, e:
|
except AttributeError, e:
|
||||||
# sys.stderr.write('AttributeError: {!s}\n'.format(e))
|
# sys.stderr.write('AttributeError: {!s}\n'.format(e))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = prop._saver(self, prop, value)
|
||||||
|
except property.DontSave:
|
||||||
|
continue
|
||||||
|
|
||||||
element = lxml.etree.Element('property', name=prop.__name__)
|
element = lxml.etree.Element('property', name=prop.__name__)
|
||||||
if prop.save_via_ref:
|
if prop.save_via_ref:
|
||||||
element.set('ref', value)
|
element.set('ref', value)
|
||||||
@ -640,8 +716,30 @@ class PropertyHolder(qubes.events.Emitter):
|
|||||||
return properties
|
return properties
|
||||||
|
|
||||||
|
|
||||||
import qubes.vm.qubesvm
|
# this was clone_attrs
|
||||||
import qubes.vm.templatevm
|
def clone_properties(self, src, proplist=None):
|
||||||
|
'''Clone properties from other object.
|
||||||
|
|
||||||
|
:param PropertyHolder src: source object
|
||||||
|
:param list proplist: list of properties (:py:obj:`None` for all properties)
|
||||||
|
'''
|
||||||
|
|
||||||
|
if proplist is None:
|
||||||
|
proplist = self.get_props_list()
|
||||||
|
else:
|
||||||
|
proplist = [prop for prop in self.get_props_list()
|
||||||
|
if prop.__name__ in proplist or prop in proplist]
|
||||||
|
|
||||||
|
for prop in self.proplist():
|
||||||
|
try:
|
||||||
|
self._init_property(self, prop, getattr(src, prop._attr_name))
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.fire_event('cloned-properties', src, proplist)
|
||||||
|
|
||||||
|
|
||||||
|
import qubes.vm
|
||||||
|
|
||||||
|
|
||||||
class VMProperty(property):
|
class VMProperty(property):
|
||||||
@ -672,6 +770,10 @@ class VMProperty(property):
|
|||||||
super(VMProperty, self).__set__(self, instance, vm)
|
super(VMProperty, self).__set__(self, instance, vm)
|
||||||
|
|
||||||
|
|
||||||
|
import qubes.vm.qubesvm
|
||||||
|
import qubes.vm.templatevm
|
||||||
|
|
||||||
|
|
||||||
class Qubes(PropertyHolder):
|
class Qubes(PropertyHolder):
|
||||||
'''Main Qubes application
|
'''Main Qubes application
|
||||||
|
|
||||||
@ -742,6 +844,12 @@ class Qubes(PropertyHolder):
|
|||||||
#: collection of all available labels for VMs
|
#: collection of all available labels for VMs
|
||||||
self.labels = {}
|
self.labels = {}
|
||||||
|
|
||||||
|
#: Connection to VMM
|
||||||
|
self.vmm = VMMConnection()
|
||||||
|
|
||||||
|
#: Information about host system
|
||||||
|
self.host = QubesHost(self)
|
||||||
|
|
||||||
self._store = store
|
self._store = store
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -749,7 +857,7 @@ class Qubes(PropertyHolder):
|
|||||||
except IOError:
|
except IOError:
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
super(PropertyHolder, self).__init__(xml=lxml.etree.parse(self.qubes_store_file))
|
super(Qubes, self).__init__(xml=lxml.etree.parse(self.qubes_store_file))
|
||||||
|
|
||||||
|
|
||||||
def _open_store(self):
|
def _open_store(self):
|
||||||
|
93
qubes/config.py
Normal file
93
qubes/config.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/python2
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||||
|
# Copyright (C) 2014 Wojtek Porczyk <joanna@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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
qubes_base_dir = "/var/lib/qubes"
|
||||||
|
system_path = {
|
||||||
|
'qubes_guid_path': '/usr/bin/qubes-guid',
|
||||||
|
'qrexec_daemon_path': '/usr/lib/qubes/qrexec-daemon',
|
||||||
|
'qrexec_client_path': '/usr/lib/qubes/qrexec-client',
|
||||||
|
'qubesdb_daemon_path': '/usr/sbin/qubesdb-daemon',
|
||||||
|
|
||||||
|
'qubes_base_dir': qubes_base_dir,
|
||||||
|
|
||||||
|
# Relative to qubes_base_dir
|
||||||
|
'qubes_appvms_dir': 'appvms',
|
||||||
|
'qubes_templates_dir': 'vm-templates',
|
||||||
|
'qubes_servicevms_dir': 'servicevms',
|
||||||
|
'qubes_store_filename': 'qubes.xml',
|
||||||
|
'qubes_kernels_base_dir': 'vm-kernels',
|
||||||
|
|
||||||
|
# qubes_icon_dir is obsolete
|
||||||
|
# use QIcon.fromTheme() where applicable
|
||||||
|
'qubes_icon_dir': '/usr/share/icons/hicolor/128x128/devices',
|
||||||
|
|
||||||
|
'qrexec_policy_dir': '/etc/qubes-rpc/policy',
|
||||||
|
|
||||||
|
'config_template_pv': '/usr/share/qubes/vm-template.xml',
|
||||||
|
|
||||||
|
'qubes_pciback_cmd': '/usr/lib/qubes/unbind-pci-device.sh',
|
||||||
|
'prepare_volatile_img_cmd': '/usr/lib/qubes/prepare-volatile-img.sh',
|
||||||
|
'monitor_layout_notify_cmd': '/usr/bin/qubes-monitor-layout-notify',
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_files = {
|
||||||
|
'root_img': 'root.img',
|
||||||
|
'rootcow_img': 'root-cow.img',
|
||||||
|
'volatile_img': 'volatile.img',
|
||||||
|
'clean_volatile_img': 'clean-volatile.img.tar',
|
||||||
|
'private_img': 'private.img',
|
||||||
|
'kernels_subdir': 'kernels',
|
||||||
|
'firewall_conf': 'firewall.xml',
|
||||||
|
'whitelisted_appmenus': 'whitelisted-appmenus.list',
|
||||||
|
'updates_stat_file': 'updates.stat',
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'libvirt_uri': 'xen:///',
|
||||||
|
'memory': 400,
|
||||||
|
'kernelopts': "nopat",
|
||||||
|
'kernelopts_pcidevs': "nopat iommu=soft swiotlb=4096",
|
||||||
|
|
||||||
|
'dom0_update_check_interval': 6*3600,
|
||||||
|
|
||||||
|
'private_img_size': 2*1024*1024*1024,
|
||||||
|
'root_img_size': 10*1024*1024*1024,
|
||||||
|
|
||||||
|
'storage_class': None,
|
||||||
|
|
||||||
|
# how long (in sec) to wait for VMs to shutdown,
|
||||||
|
# before killing them (when used qvm-run with --wait option),
|
||||||
|
'shutdown_counter_max': 60,
|
||||||
|
|
||||||
|
'vm_default_netmask': "255.255.255.0",
|
||||||
|
|
||||||
|
# Set later
|
||||||
|
'appvm_label': None,
|
||||||
|
'template_label': None,
|
||||||
|
'servicevm_label': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
max_qid = 254
|
||||||
|
max_netid = 254
|
54
qubes/utils.py
Normal file
54
qubes/utils.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/python2 -O
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||||
|
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
||||||
|
# Copyright (C) 2014 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: should be outside of QubesVM?
|
||||||
|
def get_timezone(self):
|
||||||
|
# fc18
|
||||||
|
if os.path.islink('/etc/localtime'):
|
||||||
|
return '/'.join(os.readlink('/etc/localtime').split('/')[-2:])
|
||||||
|
# <=fc17
|
||||||
|
elif os.path.exists('/etc/sysconfig/clock'):
|
||||||
|
clock_config = open('/etc/sysconfig/clock', "r")
|
||||||
|
clock_config_lines = clock_config.readlines()
|
||||||
|
clock_config.close()
|
||||||
|
zone_re = re.compile(r'^ZONE="(.*)"')
|
||||||
|
for line in clock_config_lines:
|
||||||
|
line_match = zone_re.match(line)
|
||||||
|
if line_match:
|
||||||
|
return line_match.group(1)
|
||||||
|
else:
|
||||||
|
# last resort way, some applications makes /etc/localtime
|
||||||
|
# hardlink instead of symlink...
|
||||||
|
tz_info = os.stat('/etc/localtime')
|
||||||
|
if not tz_info:
|
||||||
|
return None
|
||||||
|
if tz_info.st_nlink > 1:
|
||||||
|
p = subprocess.Popen(['find', '/usr/share/zoneinfo',
|
||||||
|
'-inum', str(tz_info.st_ino)],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
tz_path = p.communicate()[0].strip()
|
||||||
|
return tz_path.replace('/usr/share/zoneinfo/', '')
|
||||||
|
return None
|
||||||
|
|
@ -297,6 +297,302 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
self.__class__.__name__, id(self), ' '.join(proprepr))
|
self.__class__.__name__, id(self), ' '.join(proprepr))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# xml serialising methods
|
||||||
|
#
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xml_net_dev(ip, mac, backend):
|
||||||
|
'''Return ``<interface>`` node for libvirt xml.
|
||||||
|
|
||||||
|
This was previously _format_net_dev
|
||||||
|
|
||||||
|
:param str ip: IP address of the frontend
|
||||||
|
:param str mac: MAC (Ethernet) address of the frontend
|
||||||
|
:param qubes.vm.QubesVM backend: Backend domain
|
||||||
|
:rtype: lxml.etree._Element
|
||||||
|
'''
|
||||||
|
|
||||||
|
interface = lxml.etree.Element('interface', type='ethernet')
|
||||||
|
interface.append(lxml.etree.Element('mac', address=mac))
|
||||||
|
interface.append(lxml.etree.Element('ip', address=ip))
|
||||||
|
interface.append(lxml.etree.Element('domain', name=backend.name))
|
||||||
|
|
||||||
|
return interface
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def xml_pci_dev(address):
|
||||||
|
'''Return ``<hostdev>`` node for libvirt xml.
|
||||||
|
|
||||||
|
This was previously _format_pci_dev
|
||||||
|
|
||||||
|
:param str ip: IP address of the frontend
|
||||||
|
:param str mac: MAC (Ethernet) address of the frontend
|
||||||
|
:param qubes.vm.QubesVM backend: Backend domain
|
||||||
|
:rtype: lxml.etree._Element
|
||||||
|
'''
|
||||||
|
|
||||||
|
dev_match = re.match('([0-9a-f]+):([0-9a-f]+)\.([0-9a-f]+)', address)
|
||||||
|
if not dev_match:
|
||||||
|
raise QubesException("Invalid PCI device address: %s" % address)
|
||||||
|
|
||||||
|
hostdev = lxml.etree.Element('hostdev', type='pci', managed='yes')
|
||||||
|
source = lxml.etree.Element('source')
|
||||||
|
source.append(lxml.etree.Element('address',
|
||||||
|
bus='0x' + dev_match.group(1),
|
||||||
|
slot='0x' + dev_match.group(2),
|
||||||
|
function='0x' + dev_match.group(3)))
|
||||||
|
hostdev.append(source)
|
||||||
|
return hostdev
|
||||||
|
|
||||||
|
#
|
||||||
|
# old libvirt XML
|
||||||
|
# TODO rewrite it to do proper XML synthesis via lxml.etree
|
||||||
|
#
|
||||||
|
|
||||||
|
def get_config_params(self):
|
||||||
|
'''Return parameters for libvirt's XML domain config
|
||||||
|
|
||||||
|
.. deprecated:: 3.0-alpha This will go away.
|
||||||
|
'''
|
||||||
|
|
||||||
|
args = {}
|
||||||
|
args['name'] = self.name
|
||||||
|
if hasattr(self, 'kernels_dir'):
|
||||||
|
args['kerneldir'] = self.kernels_dir
|
||||||
|
args['uuidnode'] = '<uuid>{!r}</uuid>'.format(self.uuid) \
|
||||||
|
if hasattr(self, 'uuid') else ''
|
||||||
|
args['vmdir'] = self.dir_path
|
||||||
|
args['pcidevs'] = ''.join(lxml.etree.tostring(self.xml_pci_dev(dev))
|
||||||
|
for dev in self.devices['pci'])
|
||||||
|
args['maxmem'] = str(self.maxmem)
|
||||||
|
args['vcpus'] = str(self.vcpus)
|
||||||
|
args['mem'] = str(max(self.memory, self.maxmem))
|
||||||
|
|
||||||
|
if 'meminfo-writer' in self.services and not self.services['meminfo-writer']:
|
||||||
|
# If dynamic memory management disabled, set maxmem=mem
|
||||||
|
args['maxmem'] = args['mem']
|
||||||
|
|
||||||
|
if self.netvm is not None:
|
||||||
|
args['ip'] = self.ip
|
||||||
|
args['mac'] = self.mac
|
||||||
|
args['gateway'] = self.netvm.gateway
|
||||||
|
args['dns1'] = self.netvm.gateway
|
||||||
|
args['dns2'] = self.secondary_dns
|
||||||
|
args['netmask'] = self.netmask
|
||||||
|
args['netdev'] = lxml.etree.tostring(self.xml_net_dev(self.ip, self.mac, self.netvm))
|
||||||
|
args['disable_network1'] = '';
|
||||||
|
args['disable_network2'] = '';
|
||||||
|
else:
|
||||||
|
args['ip'] = ''
|
||||||
|
args['mac'] = ''
|
||||||
|
args['gateway'] = ''
|
||||||
|
args['dns1'] = ''
|
||||||
|
args['dns2'] = ''
|
||||||
|
args['netmask'] = ''
|
||||||
|
args['netdev'] = ''
|
||||||
|
args['disable_network1'] = '<!--';
|
||||||
|
args['disable_network2'] = '-->';
|
||||||
|
|
||||||
|
args.update(self.storage.get_config_params())
|
||||||
|
|
||||||
|
if hasattr(self, 'kernelopts'):
|
||||||
|
args['kernelopts'] = self.kernelopts
|
||||||
|
if self.debug:
|
||||||
|
self.log.info("Debug mode: adding 'earlyprintk=xen' to kernel opts")
|
||||||
|
args['kernelopts'] += ' earlyprintk=xen'
|
||||||
|
|
||||||
|
|
||||||
|
def create_config_file(self, file_path=None, prepare_dvm=False):
|
||||||
|
'''Create libvirt's XML domain config file
|
||||||
|
|
||||||
|
If :py:attr:`qubes.vm.qubesvm.QubesVM.uses_custom_config` is true, this
|
||||||
|
does nothing.
|
||||||
|
|
||||||
|
:param str file_path: Path to file to create (default: :py:attr:`qubes.vm.qubesvm.QubesVM.conf_file`)
|
||||||
|
:param bool prepare_dvm: If we are in the process of preparing DisposableVM
|
||||||
|
'''
|
||||||
|
|
||||||
|
if file_path is None:
|
||||||
|
file_path = self.conf_file
|
||||||
|
if self.uses_custom_config:
|
||||||
|
conf_appvm = open(file_path, "r")
|
||||||
|
domain_config = conf_appvm.read()
|
||||||
|
conf_appvm.close()
|
||||||
|
return domain_config
|
||||||
|
|
||||||
|
f_conf_template = open(self.config_file_template, 'r')
|
||||||
|
conf_template = f_conf_template.read()
|
||||||
|
f_conf_template.close()
|
||||||
|
|
||||||
|
template_params = self.get_config_params()
|
||||||
|
if 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
|
||||||
|
old_umask = os.umask(002)
|
||||||
|
try:
|
||||||
|
conf_appvm = open(file_path, "w")
|
||||||
|
conf_appvm.write(domain_config)
|
||||||
|
conf_appvm.close()
|
||||||
|
except:
|
||||||
|
# Ignore errors
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
os.umask(old_umask)
|
||||||
|
|
||||||
|
return domain_config
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# firewall
|
||||||
|
# TODO rewrite it, have <firewall/> node under <domain/>
|
||||||
|
# and possibly integrate with generic policy framework
|
||||||
|
#
|
||||||
|
|
||||||
|
def write_firewall_conf(self, conf):
|
||||||
|
'''Write firewall config file.
|
||||||
|
'''
|
||||||
|
defaults = self.get_firewall_conf()
|
||||||
|
expiring_rules_present = False
|
||||||
|
for item in defaults.keys():
|
||||||
|
if item not in conf:
|
||||||
|
conf[item] = defaults[item]
|
||||||
|
|
||||||
|
root = lxml.etree.Element(
|
||||||
|
"QubesFirewallRules",
|
||||||
|
policy = "allow" if conf["allow"] else "deny",
|
||||||
|
dns = "allow" if conf["allowDns"] else "deny",
|
||||||
|
icmp = "allow" if conf["allowIcmp"] else "deny",
|
||||||
|
yumProxy = "allow" if conf["allowYumProxy"] else "deny"
|
||||||
|
)
|
||||||
|
|
||||||
|
for rule in conf["rules"]:
|
||||||
|
# For backward compatibility
|
||||||
|
if "proto" not in rule:
|
||||||
|
if rule["portBegin"] is not None and rule["portBegin"] > 0:
|
||||||
|
rule["proto"] = "tcp"
|
||||||
|
else:
|
||||||
|
rule["proto"] = "any"
|
||||||
|
element = lxml.etree.Element(
|
||||||
|
"rule",
|
||||||
|
address=rule["address"],
|
||||||
|
proto=str(rule["proto"]),
|
||||||
|
)
|
||||||
|
if rule["netmask"] is not None and rule["netmask"] != 32:
|
||||||
|
element.set("netmask", str(rule["netmask"]))
|
||||||
|
if rule.get("portBegin", None) is not None and \
|
||||||
|
rule["portBegin"] > 0:
|
||||||
|
element.set("port", str(rule["portBegin"]))
|
||||||
|
if rule.get("portEnd", None) is not None and rule["portEnd"] > 0:
|
||||||
|
element.set("toport", str(rule["portEnd"]))
|
||||||
|
if "expire" in rule:
|
||||||
|
element.set("expire", str(rule["expire"]))
|
||||||
|
expiring_rules_present = True
|
||||||
|
|
||||||
|
root.append(element)
|
||||||
|
|
||||||
|
tree = lxml.etree.ElementTree(root)
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_umask = os.umask(002)
|
||||||
|
with open(self.firewall_conf, 'w') as f:
|
||||||
|
tree.write(f, encoding="UTF-8", pretty_print=True)
|
||||||
|
f.close()
|
||||||
|
os.umask(old_umask)
|
||||||
|
except EnvironmentError as err:
|
||||||
|
print >> sys.stderr, "{0}: save error: {1}".format(
|
||||||
|
os.path.basename(sys.argv[0]), err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Automatically enable/disable 'yum-proxy-setup' service based on allowYumProxy
|
||||||
|
if conf['allowYumProxy']:
|
||||||
|
self.services['yum-proxy-setup'] = True
|
||||||
|
else:
|
||||||
|
if self.services.has_key('yum-proxy-setup'):
|
||||||
|
self.services.pop('yum-proxy-setup')
|
||||||
|
|
||||||
|
if expiring_rules_present:
|
||||||
|
subprocess.call(["sudo", "systemctl", "start",
|
||||||
|
"qubes-reload-firewall@%s.timer" % self.name])
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def has_firewall(self):
|
||||||
|
return os.path.exists (self.firewall_conf)
|
||||||
|
|
||||||
|
def get_firewall_defaults(self):
|
||||||
|
return { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True, "allowYumProxy": False }
|
||||||
|
|
||||||
|
def get_firewall_conf(self):
|
||||||
|
conf = self.get_firewall_defaults()
|
||||||
|
|
||||||
|
try:
|
||||||
|
tree = lxml.etree.parse(self.firewall_conf)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
conf["allow"] = (root.get("policy") == "allow")
|
||||||
|
conf["allowDns"] = (root.get("dns") == "allow")
|
||||||
|
conf["allowIcmp"] = (root.get("icmp") == "allow")
|
||||||
|
conf["allowYumProxy"] = (root.get("yumProxy") == "allow")
|
||||||
|
|
||||||
|
for element in root:
|
||||||
|
rule = {}
|
||||||
|
attr_list = ("address", "netmask", "proto", "port", "toport",
|
||||||
|
"expire")
|
||||||
|
|
||||||
|
for attribute in attr_list:
|
||||||
|
rule[attribute] = element.get(attribute)
|
||||||
|
|
||||||
|
if rule["netmask"] is not None:
|
||||||
|
rule["netmask"] = int(rule["netmask"])
|
||||||
|
else:
|
||||||
|
rule["netmask"] = 32
|
||||||
|
|
||||||
|
if rule["port"] is not None:
|
||||||
|
rule["portBegin"] = int(rule["port"])
|
||||||
|
else:
|
||||||
|
# backward compatibility
|
||||||
|
rule["portBegin"] = 0
|
||||||
|
|
||||||
|
# For backward compatibility
|
||||||
|
if rule["proto"] is None:
|
||||||
|
if rule["portBegin"] > 0:
|
||||||
|
rule["proto"] = "tcp"
|
||||||
|
else:
|
||||||
|
rule["proto"] = "any"
|
||||||
|
|
||||||
|
if rule["toport"] is not None:
|
||||||
|
rule["portEnd"] = int(rule["toport"])
|
||||||
|
else:
|
||||||
|
rule["portEnd"] = None
|
||||||
|
|
||||||
|
if rule["expire"] is not None:
|
||||||
|
rule["expire"] = int(rule["expire"])
|
||||||
|
if rule["expire"] <= int(datetime.datetime.now().strftime(
|
||||||
|
"%s")):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
del(rule["expire"])
|
||||||
|
|
||||||
|
del(rule["port"])
|
||||||
|
del(rule["toport"])
|
||||||
|
|
||||||
|
conf["rules"].append(rule)
|
||||||
|
|
||||||
|
except EnvironmentError as err:
|
||||||
|
return conf
|
||||||
|
except (xml.parsers.expat.ExpatError,
|
||||||
|
ValueError, LookupError) as err:
|
||||||
|
print("{0}: load error: {1}".format(
|
||||||
|
os.path.basename(sys.argv[0]), err))
|
||||||
|
return None
|
||||||
|
|
||||||
|
return conf
|
||||||
|
|
||||||
def load(class_, D):
|
def load(class_, D):
|
||||||
cls = BaseVM[class_]
|
cls = BaseVM[class_]
|
||||||
|
@ -4,5 +4,14 @@ import qubes.vm.qubesvm
|
|||||||
|
|
||||||
class AppVM(qubes.vm.qubesvm.QubesVM):
|
class AppVM(qubes.vm.qubesvm.QubesVM):
|
||||||
'''Application VM'''
|
'''Application VM'''
|
||||||
|
|
||||||
|
template = qubes.VMProperty('template', load_stage=4,
|
||||||
|
vmclass=qubes.vm.templatevm.TemplateVM,
|
||||||
|
doc='Template, on which this AppVM is based.')
|
||||||
|
|
||||||
def __init__(self, D):
|
def __init__(self, D):
|
||||||
super(AppVM, self).__init__(D)
|
super(AppVM, self).__init__(D)
|
||||||
|
|
||||||
|
# Some additional checks for template based VM
|
||||||
|
assert self.template
|
||||||
|
self.template.appvms.add(self)
|
||||||
|
1650
qubes/vm/qubesvm.py
1650
qubes/vm/qubesvm.py
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,8 @@ import qubes.vm.qubesvm
|
|||||||
class TemplateVM(qubes.vm.qubesvm.QubesVM):
|
class TemplateVM(qubes.vm.qubesvm.QubesVM):
|
||||||
'''Template for AppVM'''
|
'''Template for AppVM'''
|
||||||
|
|
||||||
template = qubes.property('template',
|
|
||||||
setter=qubes.property.forbidden)
|
|
||||||
|
|
||||||
def __init__(self, D):
|
def __init__(self, D):
|
||||||
super(TemplateVM, self).__init__(D)
|
super(TemplateVM, self).__init__(D)
|
||||||
|
|
||||||
|
# Some additional checks for template based VM
|
||||||
|
assert self.root_img is not None, "Missing root_img for standalone VM!"
|
||||||
|
79
tests/vm/qubesvm.py
Normal file
79
tests/vm/qubesvm.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/python2 -O
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, '../../')
|
||||||
|
|
||||||
|
import qubes
|
||||||
|
import qubes.vm.qubesvm
|
||||||
|
|
||||||
|
|
||||||
|
class TestProp(object):
|
||||||
|
__name__ = 'testprop'
|
||||||
|
|
||||||
|
|
||||||
|
class TestVM(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.running = False
|
||||||
|
self.installed_by_rpm = False
|
||||||
|
|
||||||
|
def is_running(self):
|
||||||
|
return self.running
|
||||||
|
|
||||||
|
|
||||||
|
class TC_00_setters(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.vm = TestVM()
|
||||||
|
self.prop = TestProp()
|
||||||
|
|
||||||
|
|
||||||
|
def test_000_setter_qid(self):
|
||||||
|
self.assertEqual(
|
||||||
|
qubes.vm.qubesvm._setter_qid(self.vm, self.prop, 5), 5)
|
||||||
|
|
||||||
|
def test_001_setter_qid_lt_0(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
qubes.vm.qubesvm._setter_qid(self.vm, self.prop, -1)
|
||||||
|
|
||||||
|
def test_002_setter_qid_gt_max(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
qubes.vm.qubesvm._setter_qid(self.vm, self.prop, qubes.MAX_QID + 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_010_setter_name(self):
|
||||||
|
self.assertEqual(
|
||||||
|
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'test_name-1'),
|
||||||
|
'test_name-1')
|
||||||
|
|
||||||
|
def test_011_setter_name_longer_than_31(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 't' * 32)
|
||||||
|
|
||||||
|
def test_012_setter_name_illegal_character(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'test#')
|
||||||
|
|
||||||
|
def test_013_setter_name_first_not_letter(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
qubes.vm.qubesvm._setter_name(self.vm, self.prop, '1test')
|
||||||
|
|
||||||
|
def test_014_setter_name_running(self):
|
||||||
|
self.vm.running = True
|
||||||
|
with self.assertRaises(qubes.QubesException):
|
||||||
|
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'testname')
|
||||||
|
|
||||||
|
def test_015_setter_name_installed_by_rpm(self):
|
||||||
|
self.vm.installed_by_rpm = True
|
||||||
|
with self.assertRaises(qubes.QubesException):
|
||||||
|
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'testname')
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skip('test not implemented')
|
||||||
|
def test_020_setter_kernel(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TC_90_QubesVM(unittest.TestCase):
|
||||||
|
@unittest.skip('test not implemented')
|
||||||
|
def test_000_init(self):
|
||||||
|
pass
|
Loading…
Reference in New Issue
Block a user