core3 move: QubesVM

This is a big commit and probably incomplete. Tests will follow.
This commit is contained in:
Wojtek Porczyk 2014-12-29 12:46:16 +01:00
parent 6b7860995b
commit 41fef46db2
9 changed files with 2307 additions and 26 deletions

View File

@ -33,6 +33,7 @@ extensions = [
'sphinx.ext.autosummary',
'sphinx.ext.coverage',
'sphinx.ext.doctest',
'sphinx.ext.graphviz',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'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").
#html_file_suffix = None
# html links do not work with svg!
graphviz_output_format = 'png'
# Output file base name for HTML help builder.
htmlhelp_basename = 'core-admin-doc'

View File

@ -58,7 +58,7 @@ class QubesException(Exception):
'''Exception that can be shown to the user'''
pass
class QubesVMMConnection(object):
class VMMConnection(object):
'''Connection to Virtual Machine Manager (libvirt)'''
def __init__(self):
self._libvirt_conn = None
@ -118,13 +118,18 @@ class QubesVMMConnection(object):
self.init_vmm_connection()
return self._xs
vmm = QubesVMMConnection()
class QubesHost(object):
'''Basic information about host machine'''
def __init__(self):
(model, memory, cpus, mhz, nodes, socket, cores, threads) = vmm.libvirt_conn.getInfo()
'''Basic information about host machine
: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._no_cpus = cpus
@ -154,7 +159,7 @@ class QubesHost(object):
if previous is None:
previous_time = time.time()
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:
previous[vm['domid']] = {}
previous[vm['domid']]['cpu_time'] = (
@ -164,7 +169,7 @@ class QubesHost(object):
current_time = time.time()
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:
current[vm['domid']] = {}
current[vm['domid']]['cpu_time'] = (
@ -427,18 +432,38 @@ class property(object):
: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 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 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 order: order of evaluation (bigger order values are later)
: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):
self.__name__ = name
self._setter = setter
self._saver = saver if saver is not None else (lambda self, prop, value: str(value))
self._type = type
self._default = default
self.order = order
@ -484,6 +509,12 @@ class property(object):
if self._type is not None:
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)
if has_oldvalue:
@ -509,13 +540,28 @@ class property(object):
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
#
@staticmethod
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
unwanted property. When someone attempts to load such a property, it
@ -527,6 +573,22 @@ class property(object):
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):
'''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])
Fired when property changes state. Signature is variable, *oldvalue* is
present only if there was an old value.
Fired when property changes state. Signature is variable,
*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 newvalue: New value of the property
@ -625,11 +696,16 @@ class PropertyHolder(qubes.events.Emitter):
for prop in self.get_props_list():
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:
# sys.stderr.write('AttributeError: {!s}\n'.format(e))
continue
try:
value = prop._saver(self, prop, value)
except property.DontSave:
continue
element = lxml.etree.Element('property', name=prop.__name__)
if prop.save_via_ref:
element.set('ref', value)
@ -640,8 +716,30 @@ class PropertyHolder(qubes.events.Emitter):
return properties
import qubes.vm.qubesvm
import qubes.vm.templatevm
# this was clone_attrs
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):
@ -672,6 +770,10 @@ class VMProperty(property):
super(VMProperty, self).__set__(self, instance, vm)
import qubes.vm.qubesvm
import qubes.vm.templatevm
class Qubes(PropertyHolder):
'''Main Qubes application
@ -742,6 +844,12 @@ class Qubes(PropertyHolder):
#: collection of all available labels for VMs
self.labels = {}
#: Connection to VMM
self.vmm = VMMConnection()
#: Information about host system
self.host = QubesHost(self)
self._store = store
try:
@ -749,7 +857,7 @@ class Qubes(PropertyHolder):
except IOError:
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):

93
qubes/config.py Normal file
View 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
View 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

View File

@ -297,6 +297,302 @@ class BaseVM(qubes.PropertyHolder):
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):
cls = BaseVM[class_]

View File

@ -4,5 +4,14 @@ import qubes.vm.qubesvm
class AppVM(qubes.vm.qubesvm.QubesVM):
'''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):
super(AppVM, self).__init__(D)
# Some additional checks for template based VM
assert self.template
self.template.appvms.add(self)

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@ import qubes.vm.qubesvm
class TemplateVM(qubes.vm.qubesvm.QubesVM):
'''Template for AppVM'''
template = qubes.property('template',
setter=qubes.property.forbidden)
def __init__(self, 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
View 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