2015-01-19 19:02:28 +01:00
|
|
|
# pylint: disable=protected-access
|
2015-01-19 18:03:23 +01:00
|
|
|
|
|
|
|
#
|
|
|
|
# The Qubes OS Project, https://www.qubes-os.org/
|
|
|
|
#
|
|
|
|
# Copyright (C) 2014-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
|
|
# Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
|
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
|
|
# License as published by the Free Software Foundation; either
|
|
|
|
# version 2.1 of the License, or (at your option) any later version.
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# This library is distributed in the hope that it will be useful,
|
2015-01-19 18:03:23 +01:00
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-10-12 00:11:50 +02:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
# Lesser General Public License for more details.
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
2017-10-12 00:11:50 +02:00
|
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
|
|
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
2015-01-19 18:03:23 +01:00
|
|
|
#
|
2017-12-03 03:21:35 +01:00
|
|
|
import base64
|
2016-02-10 17:41:05 +01:00
|
|
|
import os
|
2019-06-09 23:03:15 +02:00
|
|
|
import subprocess
|
2019-02-23 01:39:51 +01:00
|
|
|
import tempfile
|
2014-12-29 12:46:16 +01:00
|
|
|
|
|
|
|
import unittest
|
2015-12-30 01:48:37 +01:00
|
|
|
import uuid
|
2016-02-10 17:41:05 +01:00
|
|
|
import datetime
|
2019-06-09 23:03:15 +02:00
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
|
|
import functools
|
2017-07-17 12:27:17 +02:00
|
|
|
import lxml.etree
|
2017-07-17 12:28:24 +02:00
|
|
|
import unittest.mock
|
2014-12-29 12:46:16 +01:00
|
|
|
|
2017-12-14 02:17:42 +01:00
|
|
|
import shutil
|
|
|
|
|
2014-12-29 12:46:16 +01:00
|
|
|
import qubes
|
2015-10-14 22:02:11 +02:00
|
|
|
import qubes.exc
|
2015-01-20 14:09:47 +01:00
|
|
|
import qubes.config
|
2019-11-18 23:43:19 +01:00
|
|
|
import qubes.devices
|
2017-04-01 01:25:57 +02:00
|
|
|
import qubes.vm
|
2014-12-29 12:46:16 +01:00
|
|
|
import qubes.vm.qubesvm
|
|
|
|
|
2015-01-05 14:41:59 +01:00
|
|
|
import qubes.tests
|
2017-06-03 21:57:08 +02:00
|
|
|
import qubes.tests.vm
|
2015-01-05 14:41:59 +01:00
|
|
|
|
2015-12-30 01:48:37 +01:00
|
|
|
class TestApp(object):
|
2020-05-05 15:50:41 +02:00
|
|
|
labels = {1: qubes.Label(1, '0xcc0000', 'red'),
|
|
|
|
2: qubes.Label(2, '0x00cc00', 'green'),
|
|
|
|
3: qubes.Label(3, '0x0000cc', 'blue'),
|
|
|
|
4: qubes.Label(4, '0xcccccc', 'black')}
|
2014-12-29 12:46:16 +01:00
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
def __init__(self):
|
|
|
|
self.domains = {}
|
2018-11-15 13:43:58 +01:00
|
|
|
self.host = unittest.mock.Mock()
|
|
|
|
self.host.memory_total = 4096 * 1024
|
|
|
|
|
|
|
|
class TestFeatures(dict):
|
|
|
|
def __init__(self, vm, **kwargs) -> None:
|
|
|
|
self.vm = vm
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
def check_with_template(self, feature, default):
|
|
|
|
vm = self.vm
|
|
|
|
while vm is not None:
|
|
|
|
try:
|
|
|
|
return vm.features[feature]
|
|
|
|
except KeyError:
|
|
|
|
vm = getattr(vm, 'template', None)
|
|
|
|
return default
|
2016-02-10 17:41:05 +01:00
|
|
|
|
2014-12-29 12:46:16 +01:00
|
|
|
class TestProp(object):
|
2015-01-19 19:02:28 +01:00
|
|
|
# pylint: disable=too-few-public-methods
|
2014-12-29 12:46:16 +01:00
|
|
|
__name__ = 'testprop'
|
|
|
|
|
2017-10-11 21:11:36 +02:00
|
|
|
class TestDeviceCollection(object):
|
|
|
|
def __init__(self):
|
|
|
|
self._list = []
|
|
|
|
|
|
|
|
def persistent(self):
|
|
|
|
return self._list
|
|
|
|
|
2017-12-03 03:21:35 +01:00
|
|
|
class TestQubesDB(object):
|
2019-11-18 23:43:19 +01:00
|
|
|
def __init__(self, data=None):
|
2017-12-03 03:21:35 +01:00
|
|
|
self.data = {}
|
2019-11-18 23:43:19 +01:00
|
|
|
if data:
|
|
|
|
self.data = data
|
2017-12-03 03:21:35 +01:00
|
|
|
|
|
|
|
def write(self, path, value):
|
|
|
|
self.data[path] = value
|
|
|
|
|
|
|
|
def rm(self, path):
|
|
|
|
if path.endswith('/'):
|
|
|
|
for key in [x for x in self.data if x.startswith(path)]:
|
|
|
|
del self.data[key]
|
|
|
|
else:
|
|
|
|
self.data.pop(path, None)
|
|
|
|
|
2019-11-18 23:43:19 +01:00
|
|
|
def list(self, prefix):
|
|
|
|
return [key for key in self.data if key.startswith(prefix)]
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
pass
|
|
|
|
|
2014-12-29 12:46:16 +01:00
|
|
|
class TestVM(object):
|
2015-01-19 19:02:28 +01:00
|
|
|
# pylint: disable=too-few-public-methods
|
2015-12-30 01:48:37 +01:00
|
|
|
app = TestApp()
|
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
def __init__(self, **kwargs):
|
2014-12-29 12:46:16 +01:00
|
|
|
self.running = False
|
|
|
|
self.installed_by_rpm = False
|
2016-02-10 17:41:05 +01:00
|
|
|
for k, v in kwargs.items():
|
|
|
|
setattr(self, k, v)
|
2017-10-11 21:11:36 +02:00
|
|
|
self.devices = {'pci': TestDeviceCollection()}
|
2018-11-15 13:43:58 +01:00
|
|
|
self.features = TestFeatures(self)
|
2014-12-29 12:46:16 +01:00
|
|
|
|
|
|
|
def is_running(self):
|
|
|
|
return self.running
|
|
|
|
|
|
|
|
|
2015-01-05 14:41:59 +01:00
|
|
|
class TC_00_setters(qubes.tests.QubesTestCase):
|
2014-12-29 12:46:16 +01:00
|
|
|
def setUp(self):
|
2017-04-18 10:16:14 +02:00
|
|
|
super().setUp()
|
2014-12-29 12:46:16 +01:00
|
|
|
self.vm = TestVM()
|
|
|
|
self.prop = TestProp()
|
|
|
|
|
|
|
|
|
|
|
|
def test_000_setter_qid(self):
|
|
|
|
self.assertEqual(
|
2017-09-26 13:24:22 +02:00
|
|
|
qubes.vm._setter_qid(self.vm, self.prop, 5), 5)
|
2014-12-29 12:46:16 +01:00
|
|
|
|
|
|
|
def test_001_setter_qid_lt_0(self):
|
|
|
|
with self.assertRaises(ValueError):
|
2017-09-26 13:24:22 +02:00
|
|
|
qubes.vm._setter_qid(self.vm, self.prop, -1)
|
2014-12-29 12:46:16 +01:00
|
|
|
|
|
|
|
def test_002_setter_qid_gt_max(self):
|
|
|
|
with self.assertRaises(ValueError):
|
2017-09-26 13:24:22 +02:00
|
|
|
qubes.vm._setter_qid(self.vm,
|
2015-01-20 14:09:47 +01:00
|
|
|
self.prop, qubes.config.max_qid + 5)
|
2014-12-29 12:46:16 +01:00
|
|
|
|
|
|
|
@unittest.skip('test not implemented')
|
|
|
|
def test_020_setter_kernel(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-12-30 01:48:37 +01:00
|
|
|
def test_030_setter_label_object(self):
|
|
|
|
label = TestApp.labels[1]
|
|
|
|
self.assertIs(label,
|
2017-04-01 01:25:57 +02:00
|
|
|
qubes.vm.setter_label(self.vm, self.prop, label))
|
2015-12-30 01:48:37 +01:00
|
|
|
|
|
|
|
def test_031_setter_label_getitem(self):
|
|
|
|
label = TestApp.labels[1]
|
|
|
|
self.assertIs(label,
|
2017-04-01 01:25:57 +02:00
|
|
|
qubes.vm.setter_label(self.vm, self.prop, 'label-1'))
|
2015-12-30 01:48:37 +01:00
|
|
|
|
|
|
|
# there is no check for self.app.get_label()
|
|
|
|
|
2017-07-17 12:26:10 +02:00
|
|
|
def test_040_setter_virt_mode(self):
|
|
|
|
self.assertEqual(
|
|
|
|
qubes.vm.qubesvm._setter_virt_mode(self.vm, self.prop, 'hvm'),
|
|
|
|
'hvm')
|
|
|
|
self.assertEqual(
|
|
|
|
qubes.vm.qubesvm._setter_virt_mode(self.vm, self.prop, 'HVM'),
|
|
|
|
'hvm')
|
|
|
|
self.assertEqual(
|
|
|
|
qubes.vm.qubesvm._setter_virt_mode(self.vm, self.prop, 'PV'),
|
|
|
|
'pv')
|
2017-10-11 21:11:36 +02:00
|
|
|
self.assertEqual(
|
|
|
|
qubes.vm.qubesvm._setter_virt_mode(self.vm, self.prop, 'pvh'),
|
|
|
|
'pvh')
|
|
|
|
self.vm.devices['pci']._list.append(object())
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
qubes.vm.qubesvm._setter_virt_mode(self.vm, self.prop, 'pvh')
|
2017-07-17 12:26:10 +02:00
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
qubes.vm.qubesvm._setter_virt_mode(self.vm, self.prop, 'True')
|
|
|
|
|
2018-07-11 04:35:36 +02:00
|
|
|
class TC_10_default(qubes.tests.QubesTestCase):
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.app = TestApp()
|
|
|
|
self.vm = TestVM(app=self.app)
|
|
|
|
self.prop = TestProp()
|
|
|
|
|
|
|
|
def test_000_default_with_template_simple(self):
|
|
|
|
default_getter = qubes.vm.qubesvm._default_with_template('kernel',
|
|
|
|
'dfl-kernel')
|
|
|
|
self.assertEqual(default_getter(self.vm), 'dfl-kernel')
|
|
|
|
self.vm.template = None
|
|
|
|
self.assertEqual(default_getter(self.vm), 'dfl-kernel')
|
|
|
|
self.vm.template = unittest.mock.Mock()
|
|
|
|
self.vm.template.kernel = 'template-kernel'
|
|
|
|
self.assertEqual(default_getter(self.vm), 'template-kernel')
|
|
|
|
|
|
|
|
def test_001_default_with_template_callable(self):
|
|
|
|
default_getter = qubes.vm.qubesvm._default_with_template('kernel',
|
|
|
|
lambda x: x.app.default_kernel)
|
|
|
|
self.app.default_kernel = 'global-dfl-kernel'
|
|
|
|
self.assertEqual(default_getter(self.vm), 'global-dfl-kernel')
|
|
|
|
self.vm.template = None
|
|
|
|
self.assertEqual(default_getter(self.vm), 'global-dfl-kernel')
|
|
|
|
self.vm.template = unittest.mock.Mock()
|
|
|
|
self.vm.template.kernel = 'template-kernel'
|
|
|
|
self.assertEqual(default_getter(self.vm), 'template-kernel')
|
|
|
|
|
|
|
|
def test_010_default_virt_mode(self):
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_virt_mode(self.vm),
|
|
|
|
'pvh')
|
|
|
|
self.vm.template = unittest.mock.Mock()
|
|
|
|
self.vm.template.virt_mode = 'hvm'
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_virt_mode(self.vm),
|
|
|
|
'hvm')
|
|
|
|
self.vm.template = None
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_virt_mode(self.vm),
|
|
|
|
'pvh')
|
|
|
|
self.vm.devices['pci'].persistent().append('some-dev')
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_virt_mode(self.vm),
|
|
|
|
'hvm')
|
|
|
|
|
2018-11-15 13:43:58 +01:00
|
|
|
def test_020_default_maxmem(self):
|
|
|
|
default_maxmem = 2048
|
|
|
|
self.vm.is_memory_balancing_possible = \
|
|
|
|
lambda: qubes.vm.qubesvm.QubesVM.is_memory_balancing_possible(
|
|
|
|
self.vm)
|
|
|
|
self.vm.virt_mode = 'pvh'
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm),
|
|
|
|
default_maxmem)
|
|
|
|
self.vm.virt_mode = 'hvm'
|
|
|
|
# HVM without qubes tools
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm), 0)
|
|
|
|
# just 'qrexec' feature
|
|
|
|
self.vm.features['qrexec'] = True
|
|
|
|
print(self.vm.features.check_with_template('qrexec', False))
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm),
|
|
|
|
default_maxmem)
|
|
|
|
# some supported-service.*, but not meminfo-writer
|
|
|
|
self.vm.features['supported-service.qubes-firewall'] = True
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm), 0)
|
|
|
|
# then add meminfo-writer
|
|
|
|
self.vm.features['supported-service.meminfo-writer'] = True
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm),
|
|
|
|
default_maxmem)
|
|
|
|
|
|
|
|
def test_021_default_maxmem_with_pcidevs(self):
|
|
|
|
self.vm.is_memory_balancing_possible = \
|
|
|
|
lambda: qubes.vm.qubesvm.QubesVM.is_memory_balancing_possible(
|
|
|
|
self.vm)
|
|
|
|
self.vm.devices['pci'].persistent().append('00_00.0')
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm), 0)
|
|
|
|
|
|
|
|
def test_022_default_maxmem_linux(self):
|
|
|
|
self.vm.is_memory_balancing_possible = \
|
|
|
|
lambda: qubes.vm.qubesvm.QubesVM.is_memory_balancing_possible(
|
|
|
|
self.vm)
|
|
|
|
self.vm.virt_mode = 'pvh'
|
|
|
|
self.vm.memory = 400
|
|
|
|
self.vm.features['os'] = 'Linux'
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm), 2048)
|
|
|
|
self.vm.memory = 100
|
|
|
|
self.assertEqual(qubes.vm.qubesvm._default_maxmem(self.vm), 1000)
|
|
|
|
|
2015-12-30 01:48:37 +01:00
|
|
|
|
2016-04-20 13:41:33 +02:00
|
|
|
class QubesVMTestsMixin(object):
|
|
|
|
property_no_default = object()
|
|
|
|
|
2015-12-29 20:35:04 +01:00
|
|
|
def setUp(self):
|
2016-04-20 13:41:33 +02:00
|
|
|
super(QubesVMTestsMixin, self).setUp()
|
2015-12-29 20:35:04 +01:00
|
|
|
self.app = qubes.tests.vm.TestApp()
|
2016-05-21 03:30:53 +02:00
|
|
|
self.app.vmm.offline_mode = True
|
2017-12-14 02:17:42 +01:00
|
|
|
self.app.default_kernel = None
|
2017-12-03 03:21:35 +01:00
|
|
|
# when full test run is called, extensions are loaded by earlier
|
|
|
|
# tests, but if just this test class is run, load them manually here,
|
|
|
|
# to have the same behaviour
|
|
|
|
qubes.ext.get_extensions()
|
2015-12-29 20:35:04 +01:00
|
|
|
|
2017-12-03 03:21:35 +01:00
|
|
|
def tearDown(self):
|
|
|
|
try:
|
2020-01-09 01:42:46 +01:00
|
|
|
# self.app is not a real events emiter, so make the call manually
|
|
|
|
for handler in qubes.Qubes.__handlers__.get('qubes-close'):
|
|
|
|
handler(self.app, 'qubes-close')
|
2017-12-03 03:21:35 +01:00
|
|
|
self.app.domains.close()
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
super(QubesVMTestsMixin, self).tearDown()
|
|
|
|
|
2019-11-18 23:43:19 +01:00
|
|
|
def get_vm(self, name='test', cls=qubes.vm.qubesvm.QubesVM, vm=None, **kwargs):
|
|
|
|
if not vm:
|
|
|
|
vm = cls(self.app, None,
|
|
|
|
qid=kwargs.pop('qid', 1), name=qubes.tests.VMPREFIX + name,
|
|
|
|
**kwargs)
|
2017-12-03 03:21:35 +01:00
|
|
|
self.app.domains[vm.qid] = vm
|
|
|
|
self.app.domains[vm.uuid] = vm
|
2018-11-30 02:02:31 +01:00
|
|
|
self.app.domains[vm.name] = vm
|
2017-12-03 03:21:35 +01:00
|
|
|
self.app.domains[vm] = vm
|
2017-09-19 17:01:29 +02:00
|
|
|
self.addCleanup(vm.close)
|
|
|
|
return vm
|
2015-12-30 01:48:37 +01:00
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
def assertPropertyValue(self, vm, prop_name, set_value, expected_value,
|
|
|
|
expected_xml_content=None):
|
|
|
|
# FIXME: any better exception list? or maybe all of that should be a
|
|
|
|
# single exception?
|
|
|
|
with self.assertNotRaises((ValueError, TypeError, KeyError)):
|
|
|
|
setattr(vm, prop_name, set_value)
|
2017-05-29 16:42:40 +02:00
|
|
|
self.assertEqual(getattr(vm, prop_name), expected_value)
|
2016-02-10 17:41:05 +01:00
|
|
|
if expected_xml_content is not None:
|
|
|
|
xml = vm.__xml__()
|
|
|
|
prop_xml = xml.xpath(
|
|
|
|
'./properties/property[@name=\'{}\']'.format(prop_name))
|
2017-05-29 16:42:40 +02:00
|
|
|
self.assertEqual(len(prop_xml), 1, "Property not found in XML")
|
|
|
|
self.assertEqual(prop_xml[0].text, expected_xml_content)
|
2016-02-10 17:41:05 +01:00
|
|
|
|
|
|
|
def assertPropertyInvalidValue(self, vm, prop_name, set_value):
|
|
|
|
orig_value_set = True
|
|
|
|
orig_value = None
|
|
|
|
try:
|
|
|
|
orig_value = getattr(vm, prop_name)
|
|
|
|
except AttributeError:
|
|
|
|
orig_value_set = False
|
|
|
|
# FIXME: any better exception list? or maybe all of that should be a
|
|
|
|
# single exception?
|
|
|
|
with self.assertRaises((ValueError, TypeError, KeyError)):
|
|
|
|
setattr(vm, prop_name, set_value)
|
|
|
|
if orig_value_set:
|
2017-05-29 16:42:40 +02:00
|
|
|
self.assertEqual(getattr(vm, prop_name), orig_value)
|
2016-02-10 17:41:05 +01:00
|
|
|
else:
|
|
|
|
with self.assertRaises(AttributeError):
|
|
|
|
getattr(vm, prop_name)
|
|
|
|
|
|
|
|
def assertPropertyDefaultValue(self, vm, prop_name,
|
|
|
|
expected_default=property_no_default):
|
|
|
|
if expected_default is self.property_no_default:
|
|
|
|
with self.assertRaises(AttributeError):
|
|
|
|
getattr(vm, prop_name)
|
|
|
|
else:
|
|
|
|
with self.assertNotRaises(AttributeError):
|
2017-05-29 16:42:40 +02:00
|
|
|
self.assertEqual(getattr(vm, prop_name), expected_default)
|
2016-02-10 17:41:05 +01:00
|
|
|
xml = vm.__xml__()
|
|
|
|
prop_xml = xml.xpath(
|
|
|
|
'./properties/property[@name=\'{}\']'.format(prop_name))
|
2017-05-29 16:42:40 +02:00
|
|
|
self.assertEqual(len(prop_xml), 0, "Property still found in XML")
|
2016-02-10 17:41:05 +01:00
|
|
|
|
|
|
|
def _test_generic_bool_property(self, vm, prop_name, default=False):
|
|
|
|
self.assertPropertyDefaultValue(vm, prop_name, default)
|
|
|
|
self.assertPropertyValue(vm, prop_name, False, False, 'False')
|
|
|
|
self.assertPropertyValue(vm, prop_name, True, True, 'True')
|
|
|
|
delattr(vm, prop_name)
|
|
|
|
self.assertPropertyDefaultValue(vm, prop_name, default)
|
|
|
|
self.assertPropertyValue(vm, prop_name, 'True', True, 'True')
|
|
|
|
self.assertPropertyValue(vm, prop_name, 'False', False, 'False')
|
|
|
|
self.assertPropertyInvalidValue(vm, prop_name, 'xxx')
|
|
|
|
self.assertPropertyValue(vm, prop_name, 123, True)
|
|
|
|
self.assertPropertyInvalidValue(vm, prop_name, '')
|
|
|
|
|
|
|
|
|
2017-06-03 21:57:08 +02:00
|
|
|
class TC_90_QubesVM(QubesVMTestsMixin, qubes.tests.QubesTestCase):
|
2015-01-19 17:06:30 +01:00
|
|
|
def test_000_init(self):
|
2015-12-30 01:48:37 +01:00
|
|
|
self.get_vm()
|
2015-12-29 20:35:04 +01:00
|
|
|
|
|
|
|
def test_001_init_no_qid_or_name(self):
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
qubes.vm.qubesvm.QubesVM(self.app, None,
|
|
|
|
name=qubes.tests.VMPREFIX + 'test')
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
qubes.vm.qubesvm.QubesVM(self.app, None,
|
|
|
|
qid=1)
|
|
|
|
|
|
|
|
def test_003_init_fire_domain_init(self):
|
|
|
|
class TestVM2(qubes.vm.qubesvm.QubesVM):
|
|
|
|
event_fired = False
|
|
|
|
@qubes.events.handler('domain-init')
|
2015-12-29 22:04:00 +01:00
|
|
|
def on_domain_init(self, event): # pylint: disable=unused-argument
|
2015-12-29 20:35:04 +01:00
|
|
|
self.__class__.event_fired = True
|
|
|
|
|
2015-12-29 22:04:00 +01:00
|
|
|
TestVM2(self.app, None, qid=1, name=qubes.tests.VMPREFIX + 'test')
|
2015-12-29 20:35:04 +01:00
|
|
|
self.assertTrue(TestVM2.event_fired)
|
2015-12-30 01:48:37 +01:00
|
|
|
|
|
|
|
def test_004_uuid_autogen(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertTrue(hasattr(vm, 'uuid'))
|
|
|
|
|
|
|
|
def test_100_qid(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertIsInstance(vm.qid, int)
|
|
|
|
with self.assertRaises(AttributeError):
|
|
|
|
vm.qid = 2
|
|
|
|
|
|
|
|
def test_110_name(self):
|
|
|
|
vm = self.get_vm()
|
2017-01-18 22:16:46 +01:00
|
|
|
self.assertIsInstance(vm.name, str)
|
2015-12-30 01:48:37 +01:00
|
|
|
|
|
|
|
def test_120_uuid(self):
|
|
|
|
my_uuid = uuid.uuid4()
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
self.assertIsInstance(vm.uuid, uuid.UUID)
|
|
|
|
self.assertIs(vm.uuid, my_uuid)
|
|
|
|
with self.assertRaises(AttributeError):
|
|
|
|
vm.uuid = uuid.uuid4()
|
|
|
|
|
2020-05-05 15:50:41 +02:00
|
|
|
@unittest.mock.patch("os.symlink")
|
|
|
|
def test_130_label(self, _):
|
2016-02-10 17:41:05 +01:00
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'label')
|
|
|
|
self.assertPropertyValue(vm, 'label', self.app.labels[1],
|
2020-05-05 15:50:41 +02:00
|
|
|
self.app.labels[1], 'red')
|
2016-02-10 17:41:05 +01:00
|
|
|
del vm.label
|
|
|
|
self.assertPropertyDefaultValue(vm, 'label')
|
|
|
|
self.assertPropertyValue(vm, 'label', 'red',
|
2020-05-05 15:50:41 +02:00
|
|
|
self.app.labels[1], 'red')
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyValue(vm, 'label', 'label-1',
|
2020-05-05 15:50:41 +02:00
|
|
|
self.app.labels[1], 'red')
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_131_label_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'label', 'invalid')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'label', 123)
|
|
|
|
|
2020-05-05 15:50:41 +02:00
|
|
|
@unittest.mock.patch("os.symlink")
|
|
|
|
def test_135_icon(self, _):
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM)
|
|
|
|
vm.label = "red"
|
|
|
|
self.assertEqual(vm.icon, "appvm-red")
|
|
|
|
|
|
|
|
templatevm = self.get_vm(cls=qubes.vm.templatevm.TemplateVM)
|
|
|
|
templatevm.label = "blue"
|
|
|
|
self.assertEqual(templatevm.icon, "templatevm-blue")
|
|
|
|
|
|
|
|
vm.template_for_dispvms = True
|
|
|
|
dispvm = self.get_vm(cls=qubes.vm.dispvm.DispVM, template=vm,
|
|
|
|
dispid=10)
|
|
|
|
dispvm.label = "black"
|
|
|
|
self.assertEqual(dispvm.icon, "dispvm-black")
|
|
|
|
|
|
|
|
vm = self.get_vm()
|
|
|
|
vm.label = "green"
|
|
|
|
vm.features["servicevm"] = 1
|
|
|
|
self.assertEqual(vm.icon, "servicevm-green")
|
|
|
|
|
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
def test_160_memory(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'memory', 400)
|
|
|
|
self.assertPropertyValue(vm, 'memory', 500, 500, '500')
|
|
|
|
del vm.memory
|
|
|
|
self.assertPropertyDefaultValue(vm, 'memory', 400)
|
|
|
|
self.assertPropertyValue(vm, 'memory', '500', 500, '500')
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_161_memory_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'memory', -100)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'memory', '-100')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'memory', '')
|
|
|
|
# TODO: higher than maxmem
|
|
|
|
# TODO: human readable setter (500M, 4G)?
|
|
|
|
|
|
|
|
def test_170_maxmem(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'maxmem',
|
2016-02-10 18:15:16 +01:00
|
|
|
self.app.host.memory_total / 1024 / 2)
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyValue(vm, 'maxmem', 500, 500, '500')
|
|
|
|
del vm.maxmem
|
|
|
|
self.assertPropertyDefaultValue(vm, 'maxmem',
|
2016-02-10 18:15:16 +01:00
|
|
|
self.app.host.memory_total / 1024 / 2)
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyValue(vm, 'maxmem', '500', 500, '500')
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_171_maxmem_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'maxmem', -100)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'maxmem', '-100')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'maxmem', '')
|
|
|
|
# TODO: lower than memory
|
|
|
|
# TODO: human readable setter (500M, 4G)?
|
|
|
|
|
|
|
|
def test_190_vcpus(self):
|
|
|
|
vm = self.get_vm()
|
2017-07-17 12:30:52 +02:00
|
|
|
self.assertPropertyDefaultValue(vm, 'vcpus', 2)
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyValue(vm, 'vcpus', 3, 3, '3')
|
|
|
|
del vm.vcpus
|
2017-07-17 12:30:52 +02:00
|
|
|
self.assertPropertyDefaultValue(vm, 'vcpus', 2)
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyValue(vm, 'vcpus', '3', 3, '3')
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_191_vcpus_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'vcpus', 0)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'vcpus', -2)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'vcpus', '-2')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'vcpus', '')
|
|
|
|
|
|
|
|
def test_200_debug(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self._test_generic_bool_property(vm, 'debug', False)
|
|
|
|
|
|
|
|
def test_210_installed_by_rpm(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self._test_generic_bool_property(vm, 'installed_by_rpm', False)
|
|
|
|
|
|
|
|
def test_220_include_in_backups(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self._test_generic_bool_property(vm, 'include_in_backups', True)
|
|
|
|
|
2015-12-30 01:48:37 +01:00
|
|
|
@qubes.tests.skipUnlessDom0
|
2016-02-10 17:41:05 +01:00
|
|
|
def test_250_kernel(self):
|
|
|
|
kernels = os.listdir(os.path.join(
|
|
|
|
qubes.config.qubes_base_dir,
|
|
|
|
qubes.config.system_path['qubes_kernels_base_dir']))
|
|
|
|
if not len(kernels):
|
|
|
|
self.skipTest('Needs at least one kernel installed')
|
|
|
|
self.app.default_kernel = kernels[0]
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'kernel', kernels[0])
|
|
|
|
self.assertPropertyValue(vm, 'kernel', kernels[-1], kernels[-1],
|
|
|
|
kernels[-1])
|
|
|
|
del vm.kernel
|
|
|
|
self.assertPropertyDefaultValue(vm, 'kernel', kernels[0])
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
@qubes.tests.skipUnlessDom0
|
|
|
|
def test_251_kernel_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'kernel', 123)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'kernel', 'invalid')
|
|
|
|
|
2017-06-26 01:59:39 +02:00
|
|
|
def test_252_kernel_empty(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyValue(vm, 'kernel', '', '', '')
|
|
|
|
self.assertPropertyValue(vm, 'kernel', None, '', '')
|
|
|
|
|
2019-02-23 01:39:51 +01:00
|
|
|
@unittest.mock.patch.dict(qubes.config.system_path,
|
|
|
|
{'qubes_kernels_base_dir': '/tmp'})
|
2016-02-10 17:41:05 +01:00
|
|
|
def test_260_kernelopts(self):
|
2019-02-23 01:39:51 +01:00
|
|
|
d = tempfile.mkdtemp(prefix='/tmp/')
|
|
|
|
self.addCleanup(shutil.rmtree, d)
|
|
|
|
open(d + '/vmlinuz', 'w').close()
|
|
|
|
open(d + '/initramfs', 'w').close()
|
2015-12-30 01:48:37 +01:00
|
|
|
vm = self.get_vm()
|
2019-02-23 01:39:51 +01:00
|
|
|
vm.kernel = os.path.basename(d)
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyDefaultValue(vm, 'kernelopts',
|
|
|
|
qubes.config.defaults['kernelopts'])
|
|
|
|
self.assertPropertyValue(vm, 'kernelopts', 'some options',
|
|
|
|
'some options', 'some options')
|
|
|
|
del vm.kernelopts
|
|
|
|
self.assertPropertyDefaultValue(vm, 'kernelopts',
|
|
|
|
qubes.config.defaults['kernelopts'])
|
|
|
|
self.assertPropertyValue(vm, 'kernelopts', '',
|
|
|
|
'', '')
|
|
|
|
# TODO?
|
|
|
|
# self.assertPropertyInvalidValue(vm, 'kernelopts', None),
|
2015-12-30 01:48:37 +01:00
|
|
|
|
|
|
|
@unittest.skip('test not implemented')
|
2016-02-10 17:41:05 +01:00
|
|
|
def test_261_kernelopts_pcidevs(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
# how to do that here? use dummy DeviceManager/DeviceCollection?
|
|
|
|
# Disable events?
|
|
|
|
vm.devices['pci'].attach('something')
|
|
|
|
self.assertPropertyDefaultValue(vm, 'kernelopts',
|
|
|
|
qubes.config.defaults['kernelopts_pcidevs'])
|
|
|
|
|
2019-02-23 01:39:51 +01:00
|
|
|
@unittest.mock.patch.dict(qubes.config.system_path,
|
|
|
|
{'qubes_kernels_base_dir': '/tmp'})
|
|
|
|
def test_262_kernelopts(self):
|
|
|
|
d = tempfile.mkdtemp(prefix='/tmp/')
|
|
|
|
self.addCleanup(shutil.rmtree, d)
|
|
|
|
open(d + '/vmlinuz', 'w').close()
|
|
|
|
open(d + '/initramfs', 'w').close()
|
|
|
|
with open(d + '/default-kernelopts-nopci.txt', 'w') as f:
|
|
|
|
f.write('some default options')
|
|
|
|
vm = self.get_vm()
|
|
|
|
vm.kernel = os.path.basename(d)
|
|
|
|
self.assertPropertyDefaultValue(vm, 'kernelopts',
|
|
|
|
'some default options')
|
|
|
|
self.assertPropertyValue(vm, 'kernelopts', 'some options',
|
|
|
|
'some options', 'some options')
|
|
|
|
del vm.kernelopts
|
|
|
|
self.assertPropertyDefaultValue(vm, 'kernelopts',
|
|
|
|
'some default options')
|
|
|
|
self.assertPropertyValue(vm, 'kernelopts', '',
|
|
|
|
'', '')
|
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
def test_270_qrexec_timeout(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'qrexec_timeout', 60)
|
|
|
|
self.assertPropertyValue(vm, 'qrexec_timeout', 3, 3, '3')
|
|
|
|
del vm.qrexec_timeout
|
|
|
|
self.assertPropertyDefaultValue(vm, 'qrexec_timeout', 60)
|
|
|
|
self.assertPropertyValue(vm, 'qrexec_timeout', '3', 3, '3')
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_271_qrexec_timeout_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'qrexec_timeout', -2)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'qrexec_timeout', '-2')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'qrexec_timeout', '')
|
|
|
|
|
2018-09-16 20:42:48 +02:00
|
|
|
def test_272_qrexec_timeout_global_changed(self):
|
|
|
|
self.app.default_qrexec_timeout = 123
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'qrexec_timeout', 123)
|
|
|
|
self.assertPropertyValue(vm, 'qrexec_timeout', 3, 3, '3')
|
|
|
|
del vm.qrexec_timeout
|
|
|
|
self.assertPropertyDefaultValue(vm, 'qrexec_timeout', 123)
|
|
|
|
self.assertPropertyValue(vm, 'qrexec_timeout', '3', 3, '3')
|
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
def test_280_autostart(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
# FIXME any better idea to not involve systemctl call at this stage?
|
|
|
|
vm.events_enabled = False
|
|
|
|
self._test_generic_bool_property(vm, 'autostart', False)
|
|
|
|
|
|
|
|
@qubes.tests.skipUnlessDom0
|
|
|
|
def test_281_autostart_systemd(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertFalse(os.path.exists(
|
|
|
|
'/etc/systemd/system/multi-user.target.wants/'
|
|
|
|
'qubes-vm@{}.service'.format(vm.name)),
|
|
|
|
"systemd service enabled before setting autostart")
|
|
|
|
vm.autostart = True
|
|
|
|
self.assertTrue(os.path.exists(
|
|
|
|
'/etc/systemd/system/multi-user.target.wants/'
|
|
|
|
'qubes-vm@{}.service'.format(vm.name)),
|
|
|
|
"systemd service not enabled by autostart=True")
|
|
|
|
vm.autostart = False
|
|
|
|
self.assertFalse(os.path.exists(
|
|
|
|
'/etc/systemd/system/multi-user.target.wants/'
|
|
|
|
'qubes-vm@{}.service'.format(vm.name)),
|
|
|
|
"systemd service not disabled by autostart=False")
|
|
|
|
vm.autostart = True
|
|
|
|
del vm.autostart
|
|
|
|
self.assertFalse(os.path.exists(
|
|
|
|
'/etc/systemd/system/multi-user.target.wants/'
|
|
|
|
'qubes-vm@{}.service'.format(vm.name)),
|
|
|
|
"systemd service not disabled by resetting autostart")
|
|
|
|
|
2018-12-01 05:07:32 +01:00
|
|
|
def test_290_management_dispvm(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
vm2 = self.get_vm('test2', qid=2)
|
|
|
|
self.app.management_dispvm = None
|
|
|
|
self.assertPropertyDefaultValue(vm, 'management_dispvm', None)
|
|
|
|
self.app.management_dispvm = vm
|
|
|
|
try:
|
|
|
|
self.assertPropertyDefaultValue(vm, 'management_dispvm', vm)
|
|
|
|
self.assertPropertyValue(vm, 'management_dispvm',
|
|
|
|
'test-inst-test2', vm2)
|
|
|
|
finally:
|
|
|
|
self.app.management_dispvm = None
|
|
|
|
|
|
|
|
def test_291_management_dispvm_template_based(self):
|
|
|
|
tpl = self.get_vm(name='tpl', cls=qubes.vm.templatevm.TemplateVM)
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=tpl, qid=2)
|
|
|
|
vm2 = self.get_vm('test2', qid=3)
|
|
|
|
del vm.volumes
|
|
|
|
self.app.management_dispvm = None
|
|
|
|
try:
|
|
|
|
self.assertPropertyDefaultValue(vm, 'management_dispvm', None)
|
|
|
|
self.app.management_dispvm = vm
|
|
|
|
self.assertPropertyDefaultValue(vm, 'management_dispvm', vm)
|
|
|
|
tpl.management_dispvm = vm2
|
|
|
|
self.assertPropertyDefaultValue(vm, 'management_dispvm', vm2)
|
|
|
|
self.assertPropertyValue(vm, 'management_dispvm',
|
|
|
|
'test-inst-test2', vm2)
|
|
|
|
finally:
|
|
|
|
self.app.management_dispvm = None
|
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
@unittest.skip('TODO')
|
2016-02-10 18:48:05 +01:00
|
|
|
def test_320_seamless_gui_mode(self):
|
2016-02-10 17:41:05 +01:00
|
|
|
vm = self.get_vm()
|
|
|
|
self._test_generic_bool_property(vm, 'seamless_gui_mode')
|
|
|
|
# TODO: reject setting to True when guiagent_installed is false
|
|
|
|
|
|
|
|
def test_330_mac(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
# TODO: calculate proper default here
|
|
|
|
default_mac = vm.mac
|
|
|
|
self.assertIsNotNone(default_mac)
|
|
|
|
self.assertPropertyDefaultValue(vm, 'mac', default_mac)
|
|
|
|
self.assertPropertyValue(vm, 'mac', '00:11:22:33:44:55',
|
|
|
|
'00:11:22:33:44:55', '00:11:22:33:44:55')
|
|
|
|
del vm.mac
|
|
|
|
self.assertPropertyDefaultValue(vm, 'mac', default_mac)
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_331_mac_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'mac', 123)
|
|
|
|
self.assertPropertyInvalidValue(vm, 'mac', 'invalid')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'mac', '00:11:22:33:44:55:66')
|
|
|
|
|
|
|
|
def test_340_default_user(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'default_user', 'user')
|
|
|
|
self.assertPropertyValue(vm, 'default_user', 'someuser', 'someuser',
|
|
|
|
'someuser')
|
|
|
|
del vm.default_user
|
|
|
|
self.assertPropertyDefaultValue(vm, 'default_user', 'user')
|
|
|
|
self.assertPropertyValue(vm, 'default_user', 123, '123', '123')
|
2018-02-01 01:50:42 +01:00
|
|
|
vm.default_user = 'user'
|
2016-02-10 17:41:05 +01:00
|
|
|
# TODO: check propagation for template-based VMs
|
|
|
|
|
|
|
|
@unittest.skip('TODO')
|
|
|
|
def test_350_timezone(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'timezone', 'localtime')
|
|
|
|
self.assertPropertyValue(vm, 'timezone', 0, 0, '0')
|
|
|
|
del vm.timezone
|
|
|
|
self.assertPropertyDefaultValue(vm, 'timezone', 'localtime')
|
|
|
|
self.assertPropertyValue(vm, 'timezone', '0', 0, '0')
|
|
|
|
self.assertPropertyValue(vm, 'timezone', -3600, -3600, '-3600')
|
|
|
|
self.assertPropertyValue(vm, 'timezone', 7200, 7200, '7200')
|
|
|
|
|
2016-02-10 19:26:07 +01:00
|
|
|
@unittest.skip('TODO')
|
|
|
|
def test_350_timezone_invalid(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyInvalidValue(vm, 'timezone', 'xxx')
|
|
|
|
|
2016-02-10 17:41:05 +01:00
|
|
|
@unittest.skip('TODO')
|
|
|
|
def test_360_drive(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
self.assertPropertyDefaultValue(vm, 'drive', None)
|
|
|
|
# self.execute_tests('drive', [
|
|
|
|
# ('hd:dom0:/tmp/drive.img', 'hd:dom0:/tmp/drive.img', True),
|
|
|
|
# ('hd:/tmp/drive.img', 'hd:dom0:/tmp/drive.img', True),
|
|
|
|
# ('cdrom:dom0:/tmp/drive.img', 'cdrom:dom0:/tmp/drive.img', True),
|
|
|
|
# ('cdrom:/tmp/drive.img', 'cdrom:dom0:/tmp/drive.img', True),
|
|
|
|
# ('/tmp/drive.img', 'cdrom:dom0:/tmp/drive.img', True),
|
|
|
|
# ('hd:drive.img', '', False),
|
|
|
|
# ('drive.img', '', False),
|
|
|
|
# ])
|
|
|
|
|
|
|
|
def test_400_backup_timestamp(self):
|
|
|
|
vm = self.get_vm()
|
|
|
|
timestamp = datetime.datetime(2016, 1, 1, 12, 14, 2)
|
|
|
|
timestamp_str = timestamp.strftime('%s')
|
|
|
|
self.assertPropertyDefaultValue(vm, 'backup_timestamp', None)
|
2018-01-11 03:46:39 +01:00
|
|
|
self.assertPropertyValue(vm, 'backup_timestamp', int(timestamp_str),
|
|
|
|
int(timestamp_str), timestamp_str)
|
2016-02-10 17:41:05 +01:00
|
|
|
del vm.backup_timestamp
|
|
|
|
self.assertPropertyDefaultValue(vm, 'backup_timestamp', None)
|
|
|
|
self.assertPropertyValue(vm, 'backup_timestamp', timestamp_str,
|
2018-01-11 03:46:39 +01:00
|
|
|
int(timestamp_str))
|
2016-02-10 19:26:07 +01:00
|
|
|
|
|
|
|
def test_401_backup_timestamp_invalid(self):
|
|
|
|
vm = self.get_vm()
|
2016-02-10 17:41:05 +01:00
|
|
|
self.assertPropertyInvalidValue(vm, 'backup_timestamp', 'xxx')
|
|
|
|
self.assertPropertyInvalidValue(vm, 'backup_timestamp', None)
|
2017-07-17 12:27:17 +02:00
|
|
|
|
|
|
|
def test_500_property_migrate_virt_mode(self):
|
|
|
|
xml_template = '''
|
|
|
|
<domain class="QubesVM" id="domain-1">
|
|
|
|
<properties>
|
|
|
|
<property name="qid">1</property>
|
|
|
|
<property name="name">testvm</property>
|
|
|
|
<property name="label" ref="label-1" />
|
|
|
|
<property name="hvm">{hvm_value}</property>
|
|
|
|
</properties>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
xml = lxml.etree.XML(xml_template.format(hvm_value='True'))
|
|
|
|
vm = qubes.vm.qubesvm.QubesVM(self.app, xml)
|
|
|
|
self.assertEqual(vm.virt_mode, 'hvm')
|
|
|
|
with self.assertRaises(AttributeError):
|
|
|
|
vm.hvm
|
|
|
|
|
|
|
|
xml = lxml.etree.XML(xml_template.format(hvm_value='False'))
|
|
|
|
vm = qubes.vm.qubesvm.QubesVM(self.app, xml)
|
|
|
|
self.assertEqual(vm.virt_mode, 'pv')
|
|
|
|
with self.assertRaises(AttributeError):
|
|
|
|
vm.hvm
|
2017-07-17 12:28:24 +02:00
|
|
|
|
|
|
|
def test_600_libvirt_xml_pv(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">500</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
2017-07-17 12:30:52 +02:00
|
|
|
<vcpu placement="static">2</vcpu>
|
2017-07-17 12:28:24 +02:00
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenpv">linux</type>
|
|
|
|
<kernel>/tmp/kernel/vmlinuz</kernel>
|
|
|
|
<initrd>/tmp/kernel/initramfs</initrd>
|
|
|
|
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH rd.plymouth.enable=0 plymouth.enable=0 nopat</cmdline>
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
</features>
|
|
|
|
<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>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/tmp/kernel/modules.img" />
|
|
|
|
<target dev="xvdd" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
</disk>
|
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'pv'
|
2017-12-14 02:17:42 +01:00
|
|
|
with unittest.mock.patch('qubes.config.qubes_base_dir',
|
|
|
|
'/tmp/qubes-test'):
|
|
|
|
kernel_dir = '/tmp/qubes-test/vm-kernels/dummy'
|
|
|
|
os.makedirs(kernel_dir, exist_ok=True)
|
|
|
|
open(os.path.join(kernel_dir, 'vmlinuz'), 'w').close()
|
|
|
|
open(os.path.join(kernel_dir, 'initramfs'), 'w').close()
|
|
|
|
self.addCleanup(shutil.rmtree, '/tmp/qubes-test')
|
|
|
|
vm.kernel = 'dummy'
|
2017-07-17 12:28:24 +02:00
|
|
|
# tests for storage are later
|
|
|
|
vm.volumes['kernel'] = unittest.mock.Mock(**{
|
|
|
|
'kernels_dir': '/tmp/kernel',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': None,
|
|
|
|
'block_device.return_value.path': '/tmp/kernel/modules.img',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.name': 'kernel',
|
|
|
|
})
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
|
|
|
def test_600_libvirt_xml_hvm(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
<memory unit="MiB">400</memory>
|
2017-07-17 12:28:24 +02:00
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
2017-07-17 12:30:52 +02:00
|
|
|
<vcpu placement="static">2</vcpu>
|
2017-07-17 12:28:24 +02:00
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
2017-09-15 16:19:10 +02:00
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
2017-07-17 12:28:24 +02:00
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
2017-12-14 02:17:42 +01:00
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
2017-07-17 12:28:24 +02:00
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
2017-12-14 02:17:42 +01:00
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
2019-05-06 18:47:05 +02:00
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
2017-12-14 02:17:42 +01:00
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
|
|
|
def test_600_libvirt_xml_hvm_dom0_kernel(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">500</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
2017-09-27 03:02:03 +02:00
|
|
|
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH rd.plymouth.enable=0 plymouth.enable=0 nopat</cmdline>
|
2017-07-17 12:28:24 +02:00
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
2017-12-14 02:17:42 +01:00
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
2017-07-17 12:28:24 +02:00
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
2017-09-15 16:19:10 +02:00
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
2017-07-17 12:28:24 +02:00
|
|
|
<graphics type="qubes"/>
|
2019-05-06 18:47:05 +02:00
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
2017-07-17 12:28:24 +02:00
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
vm.features['qrexec'] = True
|
2017-12-14 02:17:42 +01:00
|
|
|
with unittest.mock.patch('qubes.config.qubes_base_dir',
|
|
|
|
'/tmp/qubes-test'):
|
|
|
|
kernel_dir = '/tmp/qubes-test/vm-kernels/dummy'
|
|
|
|
os.makedirs(kernel_dir, exist_ok=True)
|
|
|
|
open(os.path.join(kernel_dir, 'vmlinuz'), 'w').close()
|
|
|
|
open(os.path.join(kernel_dir, 'initramfs'), 'w').close()
|
|
|
|
self.addCleanup(shutil.rmtree, '/tmp/qubes-test')
|
|
|
|
vm.kernel = 'dummy'
|
2017-07-17 12:28:24 +02:00
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
2017-10-11 21:11:36 +02:00
|
|
|
|
2019-02-25 04:59:46 +01:00
|
|
|
def test_600_libvirt_xml_hvm_dom0_kernel_kernelopts(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">500</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
|
|
|
<cmdline>kernel specific options nopat</cmdline>
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
2019-05-06 18:47:05 +02:00
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
2019-02-25 04:59:46 +01:00
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
|
|
|
vm.features['qrexec'] = True
|
|
|
|
with unittest.mock.patch('qubes.config.qubes_base_dir',
|
|
|
|
'/tmp/qubes-test'):
|
|
|
|
kernel_dir = '/tmp/qubes-test/vm-kernels/dummy'
|
|
|
|
os.makedirs(kernel_dir, exist_ok=True)
|
|
|
|
open(os.path.join(kernel_dir, 'vmlinuz'), 'w').close()
|
|
|
|
open(os.path.join(kernel_dir, 'initramfs'), 'w').close()
|
|
|
|
with open(os.path.join(kernel_dir,
|
|
|
|
'default-kernelopts-common.txt'), 'w') as f:
|
|
|
|
f.write('kernel specific options \n')
|
|
|
|
self.addCleanup(shutil.rmtree, '/tmp/qubes-test')
|
|
|
|
vm.kernel = 'dummy'
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
2017-10-11 21:11:36 +02:00
|
|
|
def test_600_libvirt_xml_pvh(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">500</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
2019-02-19 00:56:25 +01:00
|
|
|
<type arch="x86_64" machine="xenpvh">xenpvh</type>
|
2017-10-11 21:11:36 +02:00
|
|
|
<kernel>/tmp/kernel/vmlinuz</kernel>
|
|
|
|
<initrd>/tmp/kernel/initramfs</initrd>
|
|
|
|
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH rd.plymouth.enable=0 plymouth.enable=0 nopat</cmdline>
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<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>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/tmp/kernel/modules.img" />
|
|
|
|
<target dev="xvdd" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
</disk>
|
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'pvh'
|
2017-12-14 02:17:42 +01:00
|
|
|
with unittest.mock.patch('qubes.config.qubes_base_dir',
|
|
|
|
'/tmp/qubes-test'):
|
|
|
|
kernel_dir = '/tmp/qubes-test/vm-kernels/dummy'
|
|
|
|
os.makedirs(kernel_dir, exist_ok=True)
|
|
|
|
open(os.path.join(kernel_dir, 'vmlinuz'), 'w').close()
|
|
|
|
open(os.path.join(kernel_dir, 'initramfs'), 'w').close()
|
|
|
|
self.addCleanup(shutil.rmtree, '/tmp/qubes-test')
|
|
|
|
vm.kernel = 'dummy'
|
2017-10-11 21:11:36 +02:00
|
|
|
# tests for storage are later
|
|
|
|
vm.volumes['kernel'] = unittest.mock.Mock(**{
|
|
|
|
'kernels_dir': '/tmp/kernel',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': None,
|
|
|
|
'block_device.return_value.path': '/tmp/kernel/modules.img',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.name': 'kernel',
|
|
|
|
})
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
2017-12-03 03:21:35 +01:00
|
|
|
|
2018-11-15 10:14:57 +01:00
|
|
|
def test_600_libvirt_xml_pvh_no_membalance(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">400</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
2019-02-19 00:56:25 +01:00
|
|
|
<type arch="x86_64" machine="xenpvh">xenpvh</type>
|
2018-11-15 10:14:57 +01:00
|
|
|
<kernel>/tmp/kernel/vmlinuz</kernel>
|
|
|
|
<initrd>/tmp/kernel/initramfs</initrd>
|
|
|
|
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH rd.plymouth.enable=0 plymouth.enable=0 nopat</cmdline>
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<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>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/tmp/kernel/modules.img" />
|
|
|
|
<target dev="xvdd" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
</disk>
|
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'pvh'
|
|
|
|
vm.maxmem = 0
|
|
|
|
with unittest.mock.patch('qubes.config.qubes_base_dir',
|
|
|
|
'/tmp/qubes-test'):
|
|
|
|
kernel_dir = '/tmp/qubes-test/vm-kernels/dummy'
|
|
|
|
os.makedirs(kernel_dir, exist_ok=True)
|
|
|
|
open(os.path.join(kernel_dir, 'vmlinuz'), 'w').close()
|
|
|
|
open(os.path.join(kernel_dir, 'initramfs'), 'w').close()
|
|
|
|
self.addCleanup(shutil.rmtree, '/tmp/qubes-test')
|
|
|
|
vm.kernel = 'dummy'
|
|
|
|
# tests for storage are later
|
|
|
|
vm.volumes['kernel'] = unittest.mock.Mock(**{
|
|
|
|
'kernels_dir': '/tmp/kernel',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': None,
|
|
|
|
'block_device.return_value.path': '/tmp/kernel/modules.img',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.name': 'kernel',
|
|
|
|
})
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
|
|
|
def test_600_libvirt_xml_hvm_pcidev(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">400</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
<xen>
|
|
|
|
<e820_host state="on"/>
|
|
|
|
</xen>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
|
|
|
<hostdev type="pci" managed="yes">
|
|
|
|
<source>
|
|
|
|
<address
|
|
|
|
bus="0x00"
|
|
|
|
slot="0x00"
|
|
|
|
function="0x0" />
|
|
|
|
</source>
|
|
|
|
</hostdev>
|
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
2019-05-06 18:47:05 +02:00
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
2018-11-15 10:14:57 +01:00
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
# required for PCI devices listing
|
|
|
|
self.app.vmm.offline_mode = False
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
|
|
|
vm.kernel = None
|
|
|
|
# even with meminfo-writer enabled, should have memory==maxmem
|
|
|
|
vm.features['service.meminfo-writer'] = True
|
|
|
|
assignment = qubes.devices.DeviceAssignment(
|
|
|
|
vm, # this is violation of API, but for PCI the argument
|
|
|
|
# is unused
|
|
|
|
'00_00.0',
|
|
|
|
bus='pci',
|
|
|
|
persistent=True)
|
|
|
|
vm.devices['pci']._set.add(
|
|
|
|
assignment)
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
2017-10-11 21:11:36 +02:00
|
|
|
lxml.etree.XML(expected))
|
2017-12-03 03:21:35 +01:00
|
|
|
|
2019-11-18 05:10:09 +01:00
|
|
|
def test_600_libvirt_xml_hvm_cdrom_boot(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">400</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
|
|
|
<disk type="block" device="cdrom">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/sda" />
|
|
|
|
<!-- prefer xvdd for CDROM -->
|
|
|
|
<target dev="xvdd" />
|
|
|
|
<readonly/>
|
|
|
|
</disk>
|
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
qdb = {
|
|
|
|
'/qubes-block-devices/sda': b'',
|
|
|
|
'/qubes-block-devices/sda/desc': b'Test device',
|
|
|
|
'/qubes-block-devices/sda/size': b'1024000',
|
|
|
|
'/qubes-block-devices/sda/mode': b'r',
|
|
|
|
}
|
|
|
|
test_qdb = TestQubesDB(qdb)
|
|
|
|
dom0 = qubes.vm.adminvm.AdminVM(self.app, None)
|
|
|
|
dom0._qdb_connection = test_qdb
|
|
|
|
self.get_vm('dom0', vm=dom0)
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
|
|
|
vm.kernel = None
|
|
|
|
dom0.events_enabled = True
|
|
|
|
self.app.vmm.offline_mode = False
|
|
|
|
dev = qubes.devices.DeviceAssignment(
|
|
|
|
dom0, 'sda',
|
|
|
|
{'devtype': 'cdrom', 'read-only': 'yes'}, persistent=True)
|
|
|
|
self.loop.run_until_complete(vm.devices['block'].attach(dev))
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
|
|
|
def test_600_libvirt_xml_hvm_cdrom_dom0_kernel_boot(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">400</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
|
|
|
<cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH rd.plymouth.enable=0 plymouth.enable=0 nopat</cmdline>
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/tmp/kernel/modules.img" />
|
|
|
|
<target dev="xvdd" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
</disk>
|
|
|
|
<disk type="block" device="cdrom">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/sda" />
|
|
|
|
<target dev="xvdi" />
|
|
|
|
<readonly/>
|
|
|
|
</disk>
|
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
qdb = {
|
|
|
|
'/qubes-block-devices/sda': b'',
|
|
|
|
'/qubes-block-devices/sda/desc': b'Test device',
|
|
|
|
'/qubes-block-devices/sda/size': b'1024000',
|
|
|
|
'/qubes-block-devices/sda/mode': b'r',
|
|
|
|
}
|
|
|
|
test_qdb = TestQubesDB(qdb)
|
|
|
|
dom0 = qubes.vm.adminvm.AdminVM(self.app, None)
|
|
|
|
dom0._qdb_connection = test_qdb
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
|
|
|
with unittest.mock.patch('qubes.config.qubes_base_dir',
|
|
|
|
'/tmp/qubes-test'):
|
|
|
|
kernel_dir = '/tmp/qubes-test/vm-kernels/dummy'
|
|
|
|
os.makedirs(kernel_dir, exist_ok=True)
|
|
|
|
open(os.path.join(kernel_dir, 'vmlinuz'), 'w').close()
|
|
|
|
open(os.path.join(kernel_dir, 'initramfs'), 'w').close()
|
|
|
|
self.addCleanup(shutil.rmtree, '/tmp/qubes-test')
|
|
|
|
vm.kernel = 'dummy'
|
|
|
|
# tests for storage are later
|
|
|
|
vm.volumes['kernel'] = unittest.mock.Mock(**{
|
|
|
|
'kernels_dir': '/tmp/kernel',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': None,
|
|
|
|
'block_device.return_value.path': '/tmp/kernel/modules.img',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.name': 'kernel',
|
|
|
|
})
|
|
|
|
dom0.events_enabled = True
|
|
|
|
self.app.vmm.offline_mode = False
|
|
|
|
dev = qubes.devices.DeviceAssignment(
|
|
|
|
dom0, 'sda',
|
|
|
|
{'devtype': 'cdrom', 'read-only': 'yes'}, persistent=True)
|
|
|
|
self.loop.run_until_complete(vm.devices['block'].attach(dev))
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
2017-12-03 03:22:28 +01:00
|
|
|
def test_610_libvirt_xml_network(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">500</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
|
|
|
<interface type="ethernet">
|
2019-05-18 13:54:52 +02:00
|
|
|
<mac address="00:16:3e:5e:6c:00" />
|
2017-12-03 03:22:28 +01:00
|
|
|
<ip address="10.137.0.1" />
|
|
|
|
{extra_ip}
|
|
|
|
<backenddomain name="test-inst-netvm" />
|
|
|
|
<script path="vif-route-qubes" />
|
|
|
|
</interface>
|
2017-12-14 02:17:42 +01:00
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
2020-01-28 19:44:23 +01:00
|
|
|
<emulator type="stubdom-linux" cmdline="-qubes-net:client_ip=10.137.0.1,dns_0=10.139.1.1,dns_1=10.139.1.2,gw=10.137.0.2,netmask=255.255.255.255" />
|
2017-12-03 03:22:28 +01:00
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
2019-05-06 18:47:05 +02:00
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
2017-12-03 03:22:28 +01:00
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
netvm = self.get_vm(qid=2, name='netvm', provides_network=True)
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = netvm
|
|
|
|
vm.virt_mode = 'hvm'
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
vm.features['qrexec'] = True
|
2017-12-03 03:22:28 +01:00
|
|
|
with self.subTest('ipv4_only'):
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected.format(extra_ip='')))
|
|
|
|
with self.subTest('ipv6'):
|
|
|
|
netvm.features['ipv6'] = True
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected.format(
|
|
|
|
extra_ip='<ip address="{}::a89:1" family=\'ipv6\'/>'.format(
|
2017-12-06 15:23:47 +01:00
|
|
|
qubes.config.qubes_ipv6_prefix.replace(':0000', '')))))
|
2017-12-03 03:22:28 +01:00
|
|
|
|
2020-01-22 11:10:50 +01:00
|
|
|
def test_615_libvirt_xml_block_devices(self):
|
|
|
|
expected = '''<domain type="xen">
|
|
|
|
<name>test-inst-test</name>
|
|
|
|
<uuid>7db78950-c467-4863-94d1-af59806384ea</uuid>
|
|
|
|
<memory unit="MiB">400</memory>
|
|
|
|
<currentMemory unit="MiB">400</currentMemory>
|
|
|
|
<vcpu placement="static">2</vcpu>
|
|
|
|
<cpu mode='host-passthrough'>
|
|
|
|
<!-- disable nested HVM -->
|
|
|
|
<feature name='vmx' policy='disable'/>
|
|
|
|
<feature name='svm' policy='disable'/>
|
|
|
|
<!-- disable SMAP inside VM, because of Linux bug -->
|
|
|
|
<feature name='smap' policy='disable'/>
|
|
|
|
</cpu>
|
|
|
|
<os>
|
|
|
|
<type arch="x86_64" machine="xenfv">hvm</type>
|
|
|
|
<!--
|
|
|
|
For the libxl backend libvirt switches between OVMF (UEFI)
|
|
|
|
and SeaBIOS based on the loader type. This has nothing to
|
|
|
|
do with the hvmloader binary.
|
|
|
|
-->
|
|
|
|
<loader type="rom">hvmloader</loader>
|
|
|
|
<boot dev="cdrom" />
|
|
|
|
<boot dev="hd" />
|
|
|
|
</os>
|
|
|
|
<features>
|
|
|
|
<pae/>
|
|
|
|
<acpi/>
|
|
|
|
<apic/>
|
|
|
|
<viridian/>
|
|
|
|
</features>
|
|
|
|
<clock offset="variable" adjustment="0" basis="localtime" />
|
|
|
|
<on_poweroff>destroy</on_poweroff>
|
|
|
|
<on_reboot>destroy</on_reboot>
|
|
|
|
<on_crash>destroy</on_crash>
|
|
|
|
<devices>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/loop0" />
|
|
|
|
<target dev="xvda" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
<script path="/tmp/script" />
|
|
|
|
</disk>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/loop1" />
|
|
|
|
<target dev="xvde" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
</disk>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/loop2" />
|
|
|
|
<target dev="xvdf" />
|
|
|
|
<backenddomain name="dom0" />
|
|
|
|
</disk>
|
|
|
|
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/sdb" />
|
|
|
|
<target dev="xvdl" />
|
|
|
|
</disk>
|
|
|
|
<disk type="block" device="cdrom">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/sda" />
|
|
|
|
<!-- prefer xvdd for CDROM -->
|
|
|
|
<target dev="xvdd" />
|
|
|
|
</disk>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/loop0" />
|
|
|
|
<target dev="xvdi" />
|
|
|
|
<backenddomain name="backend0" />
|
|
|
|
</disk>
|
|
|
|
<disk type="block" device="disk">
|
|
|
|
<driver name="phy" />
|
|
|
|
<source dev="/dev/loop0" />
|
|
|
|
<target dev="xvdj" />
|
|
|
|
<backenddomain name="backend1" />
|
|
|
|
</disk>
|
|
|
|
<!-- server_ip is the address of stubdomain. It hosts it's own DNS server. -->
|
|
|
|
<emulator type="stubdom-linux" />
|
|
|
|
<input type="tablet" bus="usb"/>
|
|
|
|
<video>
|
|
|
|
<model type="vga"/>
|
|
|
|
</video>
|
|
|
|
<graphics type="qubes"/>
|
|
|
|
<console type="pty">
|
|
|
|
<target type="xen" port="0"/>
|
|
|
|
</console>
|
|
|
|
</devices>
|
|
|
|
</domain>
|
|
|
|
'''
|
|
|
|
my_uuid = '7db78950-c467-4863-94d1-af59806384ea'
|
|
|
|
vm = self.get_vm(uuid=my_uuid)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.virt_mode = 'hvm'
|
|
|
|
vm.volumes['root'] = unittest.mock.Mock(**{
|
|
|
|
'block_device.return_value.name': 'root',
|
|
|
|
'block_device.return_value.path': '/dev/loop0',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': '/tmp/script',
|
|
|
|
})
|
|
|
|
vm.volumes['other'] = unittest.mock.Mock(**{
|
|
|
|
'block_device.return_value.name': 'other',
|
|
|
|
'block_device.return_value.path': '/dev/loop1',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': None,
|
|
|
|
})
|
|
|
|
vm.volumes['other2'] = unittest.mock.Mock(**{
|
|
|
|
'block_device.return_value.name': 'other',
|
|
|
|
'block_device.return_value.path': '/dev/loop2',
|
|
|
|
'block_device.return_value.devtype': 'disk',
|
|
|
|
'block_device.return_value.domain': 'dom0',
|
|
|
|
'block_device.return_value.script': None,
|
|
|
|
})
|
|
|
|
assignments = [
|
|
|
|
unittest.mock.Mock(**{
|
|
|
|
'options': {'frontend-dev': 'xvdl'},
|
|
|
|
'device.device_node': '/dev/sdb',
|
|
|
|
'device.backend_domain.name': 'dom0',
|
|
|
|
}),
|
|
|
|
unittest.mock.Mock(**{
|
|
|
|
'options': {'devtype': 'cdrom'},
|
|
|
|
'device.device_node': '/dev/sda',
|
|
|
|
'device.backend_domain.name': 'dom0',
|
|
|
|
}),
|
|
|
|
unittest.mock.Mock(**{
|
|
|
|
'options': {'read-only': True},
|
|
|
|
'device.device_node': '/dev/loop0',
|
|
|
|
'device.backend_domain.name': 'backend0',
|
|
|
|
}),
|
|
|
|
unittest.mock.Mock(**{
|
|
|
|
'options': {},
|
|
|
|
'device.device_node': '/dev/loop0',
|
|
|
|
'device.backend_domain.name': 'backend1',
|
|
|
|
}),
|
|
|
|
]
|
|
|
|
vm.devices['block'].assignments = lambda persistent: assignments
|
|
|
|
libvirt_xml = vm.create_config_file()
|
|
|
|
self.assertXMLEqual(lxml.etree.XML(libvirt_xml),
|
|
|
|
lxml.etree.XML(expected))
|
|
|
|
|
2017-12-03 03:21:35 +01:00
|
|
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
|
|
|
@unittest.mock.patch('qubes.utils.urandom')
|
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
|
|
|
def test_620_qdb_standalone(self, mock_qubesdb, mock_urandom,
|
|
|
|
mock_timezone):
|
|
|
|
mock_urandom.return_value = b'A' * 64
|
|
|
|
mock_timezone.return_value = 'UTC'
|
|
|
|
vm = self.get_vm(cls=qubes.vm.standalonevm.StandaloneVM)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.events_enabled = True
|
|
|
|
test_qubesdb = TestQubesDB()
|
|
|
|
mock_qubesdb.write.side_effect = test_qubesdb.write
|
|
|
|
mock_qubesdb.rm.side_effect = test_qubesdb.rm
|
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.maxDiff = None
|
|
|
|
|
|
|
|
iptables_header = (
|
|
|
|
'# Generated by Qubes Core on {}\n'
|
|
|
|
'*filter\n'
|
|
|
|
':INPUT DROP [0:0]\n'
|
|
|
|
':FORWARD DROP [0:0]\n'
|
|
|
|
':OUTPUT ACCEPT [0:0]\n'
|
|
|
|
'-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP\n'
|
|
|
|
'-A INPUT -m conntrack --ctstate '
|
|
|
|
'RELATED,ESTABLISHED -j ACCEPT\n'
|
|
|
|
'-A INPUT -p icmp -j ACCEPT\n'
|
|
|
|
'-A INPUT -i lo -j ACCEPT\n'
|
|
|
|
'-A INPUT -j REJECT --reject-with '
|
|
|
|
'icmp-host-prohibited\n'
|
|
|
|
'-A FORWARD -m conntrack --ctstate '
|
|
|
|
'RELATED,ESTABLISHED -j ACCEPT\n'
|
|
|
|
'-A FORWARD -i vif+ -o vif+ -j DROP\n'
|
|
|
|
'COMMIT\n'.format(datetime.datetime.now().ctime()))
|
|
|
|
|
|
|
|
self.assertEqual(test_qubesdb.data, {
|
|
|
|
'/name': 'test-inst-test',
|
|
|
|
'/type': 'StandaloneVM',
|
2018-02-01 01:50:42 +01:00
|
|
|
'/default-user': 'user',
|
2017-12-03 03:21:35 +01:00
|
|
|
'/qubes-vm-type': 'AppVM',
|
|
|
|
'/qubes-debug-mode': '0',
|
|
|
|
'/qubes-base-template': '',
|
|
|
|
'/qubes-timezone': 'UTC',
|
|
|
|
'/qubes-random-seed': base64.b64encode(b'A' * 64),
|
|
|
|
'/qubes-vm-persistence': 'full',
|
|
|
|
'/qubes-vm-updateable': 'True',
|
|
|
|
'/qubes-block-devices': '',
|
|
|
|
'/qubes-usb-devices': '',
|
|
|
|
'/qubes-iptables': 'reload',
|
|
|
|
'/qubes-iptables-error': '',
|
|
|
|
'/qubes-iptables-header': iptables_header,
|
|
|
|
'/qubes-service/qubes-update-check': '0',
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
'/qubes-service/meminfo-writer': '1',
|
2020-01-09 16:41:14 +01:00
|
|
|
'/connected-ips': '',
|
|
|
|
'/connected-ips6': '',
|
2017-12-03 03:21:35 +01:00
|
|
|
})
|
|
|
|
|
2019-02-27 15:38:22 +01:00
|
|
|
@unittest.mock.patch('datetime.datetime')
|
2017-12-03 03:21:35 +01:00
|
|
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
|
|
|
@unittest.mock.patch('qubes.utils.urandom')
|
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
2017-12-14 02:09:31 +01:00
|
|
|
def test_621_qdb_vm_with_network(self, mock_qubesdb, mock_urandom,
|
2019-02-27 15:38:22 +01:00
|
|
|
mock_timezone, mock_datetime):
|
2017-12-03 03:21:35 +01:00
|
|
|
mock_urandom.return_value = b'A' * 64
|
|
|
|
mock_timezone.return_value = 'UTC'
|
|
|
|
template = self.get_vm(cls=qubes.vm.templatevm.TemplateVM, name='template')
|
|
|
|
template.netvm = None
|
2017-12-14 02:09:31 +01:00
|
|
|
netvm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
2017-12-03 03:21:35 +01:00
|
|
|
name='netvm', qid=2, provides_network=True)
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='appvm', qid=3)
|
|
|
|
vm.netvm = netvm
|
2017-12-14 02:09:31 +01:00
|
|
|
vm.kernel = None
|
|
|
|
# pretend the VM is running...
|
|
|
|
vm._qubesprop_xid = 3
|
|
|
|
netvm.kernel = None
|
2017-12-03 03:21:35 +01:00
|
|
|
test_qubesdb = TestQubesDB()
|
|
|
|
mock_qubesdb.write.side_effect = test_qubesdb.write
|
|
|
|
mock_qubesdb.rm.side_effect = test_qubesdb.rm
|
|
|
|
self.maxDiff = None
|
2019-02-27 15:38:22 +01:00
|
|
|
mock_datetime.now.returnvalue = \
|
|
|
|
datetime.datetime(2019, 2, 27, 15, 12, 15, 385822)
|
2017-12-03 03:21:35 +01:00
|
|
|
|
|
|
|
iptables_header = (
|
|
|
|
'# Generated by Qubes Core on {}\n'
|
|
|
|
'*filter\n'
|
|
|
|
':INPUT DROP [0:0]\n'
|
|
|
|
':FORWARD DROP [0:0]\n'
|
|
|
|
':OUTPUT ACCEPT [0:0]\n'
|
|
|
|
'-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP\n'
|
|
|
|
'-A INPUT -m conntrack --ctstate '
|
|
|
|
'RELATED,ESTABLISHED -j ACCEPT\n'
|
|
|
|
'-A INPUT -p icmp -j ACCEPT\n'
|
|
|
|
'-A INPUT -i lo -j ACCEPT\n'
|
|
|
|
'-A INPUT -j REJECT --reject-with '
|
|
|
|
'icmp-host-prohibited\n'
|
|
|
|
'-A FORWARD -m conntrack --ctstate '
|
|
|
|
'RELATED,ESTABLISHED -j ACCEPT\n'
|
|
|
|
'-A FORWARD -i vif+ -o vif+ -j DROP\n'
|
|
|
|
'COMMIT\n'.format(datetime.datetime.now().ctime()))
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'/name': 'test-inst-appvm',
|
|
|
|
'/type': 'AppVM',
|
2018-02-01 02:03:05 +01:00
|
|
|
'/default-user': 'user',
|
2017-12-03 03:21:35 +01:00
|
|
|
'/qubes-vm-type': 'AppVM',
|
|
|
|
'/qubes-debug-mode': '0',
|
|
|
|
'/qubes-base-template': 'test-inst-template',
|
|
|
|
'/qubes-timezone': 'UTC',
|
|
|
|
'/qubes-random-seed': base64.b64encode(b'A' * 64),
|
|
|
|
'/qubes-vm-persistence': 'rw-only',
|
|
|
|
'/qubes-vm-updateable': 'False',
|
|
|
|
'/qubes-block-devices': '',
|
|
|
|
'/qubes-usb-devices': '',
|
|
|
|
'/qubes-iptables': 'reload',
|
|
|
|
'/qubes-iptables-error': '',
|
|
|
|
'/qubes-iptables-header': iptables_header,
|
|
|
|
'/qubes-service/qubes-update-check': '0',
|
Use maxmem=0 to disable qmemman, add more automation to it
Use maxmem=0 for disabling dynamic memory balance, instead of cryptic
service.meminfo-writer feature. Under the hood, meminfo-writer service
is also set based on maxmem property (directly in qubesdb, not
vm.features dict).
Having this as a property (not "feature"), allow to have sensible
handling of default value. Specifically, disable it automatically if
otherwise it would crash a VM. This is the case for:
- domain with PCI devices (PoD is not supported by Xen then)
- domain without balloon driver and/or meminfo-writer service
The check for the latter is heuristic (assume presence of 'qrexec' also
can indicate balloon driver support), but it is true for currently
supported systems.
This also allows more reliable control of libvirt config: do not set
memory != maxmem, unless qmemman is enabled.
memory != maxmem only makes sense if qmemman for given domain is
enabled. Besides wasting some domain resources for extra page tables
etc, for HVM domains this is harmful, because maxmem-memory difference
is made of Popupate-on-Demand pool, which - when depleted - will kill
the domain. This means domain without balloon driver will die as soon
as will try to use more than initial memory - but without balloon driver
it sees maxmem memory and doesn't know about the lower limit.
Fixes QubesOS/qubes-issues#4135
2018-11-03 05:13:23 +01:00
|
|
|
'/qubes-service/meminfo-writer': '1',
|
2019-05-18 13:54:52 +02:00
|
|
|
'/qubes-mac': '00:16:3e:5e:6c:00',
|
2017-12-03 03:21:35 +01:00
|
|
|
'/qubes-ip': '10.137.0.3',
|
|
|
|
'/qubes-netmask': '255.255.255.255',
|
|
|
|
'/qubes-gateway': '10.137.0.2',
|
|
|
|
'/qubes-primary-dns': '10.139.1.1',
|
|
|
|
'/qubes-secondary-dns': '10.139.1.2',
|
2020-01-09 16:41:14 +01:00
|
|
|
'/connected-ips': '',
|
|
|
|
'/connected-ips6': '',
|
2017-12-03 03:21:35 +01:00
|
|
|
}
|
|
|
|
|
2017-12-03 03:22:28 +01:00
|
|
|
with self.subTest('ipv4'):
|
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
|
|
|
test_qubesdb.data.clear()
|
|
|
|
with self.subTest('ipv6'):
|
|
|
|
netvm.features['ipv6'] = True
|
2017-12-06 15:23:47 +01:00
|
|
|
expected['/qubes-ip6'] = \
|
|
|
|
qubes.config.qubes_ipv6_prefix.replace(':0000', '') + \
|
|
|
|
'::a89:3'
|
2018-04-02 23:59:22 +02:00
|
|
|
expected['/qubes-gateway6'] = expected['/qubes-ip6'][:-1] + '2'
|
2017-12-03 03:22:28 +01:00
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
|
|
|
test_qubesdb.data.clear()
|
|
|
|
with self.subTest('ipv6_just_appvm'):
|
|
|
|
del netvm.features['ipv6']
|
|
|
|
vm.features['ipv6'] = True
|
2017-12-06 15:23:47 +01:00
|
|
|
expected['/qubes-ip6'] = \
|
|
|
|
qubes.config.qubes_ipv6_prefix.replace(':0000', '') + \
|
|
|
|
'::a89:3'
|
2017-12-03 03:22:28 +01:00
|
|
|
del expected['/qubes-gateway6']
|
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
2017-12-14 02:09:31 +01:00
|
|
|
|
|
|
|
test_qubesdb.data.clear()
|
|
|
|
with self.subTest('proxy_ipv4'):
|
|
|
|
del vm.features['ipv6']
|
|
|
|
expected['/name'] = 'test-inst-netvm'
|
|
|
|
expected['/qubes-vm-type'] = 'NetVM'
|
|
|
|
del expected['/qubes-ip']
|
|
|
|
del expected['/qubes-gateway']
|
|
|
|
del expected['/qubes-netmask']
|
|
|
|
del expected['/qubes-ip6']
|
|
|
|
del expected['/qubes-primary-dns']
|
|
|
|
del expected['/qubes-secondary-dns']
|
2019-05-18 13:54:52 +02:00
|
|
|
del expected['/qubes-mac']
|
2017-12-14 02:09:31 +01:00
|
|
|
expected['/qubes-netvm-primary-dns'] = '10.139.1.1'
|
|
|
|
expected['/qubes-netvm-secondary-dns'] = '10.139.1.2'
|
|
|
|
expected['/qubes-netvm-network'] = '10.137.0.2'
|
|
|
|
expected['/qubes-netvm-gateway'] = '10.137.0.2'
|
|
|
|
expected['/qubes-netvm-netmask'] = '255.255.255.255'
|
|
|
|
expected['/qubes-iptables-domainrules/3'] = \
|
|
|
|
'*filter\n' \
|
|
|
|
'-A FORWARD -s 10.137.0.3 -j ACCEPT\n' \
|
|
|
|
'-A FORWARD -s 10.137.0.3 -j DROP\n' \
|
|
|
|
'COMMIT\n'
|
|
|
|
expected['/mapped-ip/10.137.0.3/visible-ip'] = '10.137.0.3'
|
|
|
|
expected['/mapped-ip/10.137.0.3/visible-gateway'] = '10.137.0.2'
|
|
|
|
expected['/qubes-firewall/10.137.0.3'] = ''
|
|
|
|
expected['/qubes-firewall/10.137.0.3/0000'] = 'action=accept'
|
|
|
|
expected['/qubes-firewall/10.137.0.3/policy'] = 'drop'
|
2020-01-09 16:41:14 +01:00
|
|
|
expected['/connected-ips'] = '10.137.0.3'
|
2017-12-14 02:09:31 +01:00
|
|
|
|
|
|
|
with unittest.mock.patch('qubes.vm.qubesvm.QubesVM.is_running',
|
|
|
|
lambda _: True):
|
|
|
|
netvm.create_qdb_entries()
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
|
|
|
test_qubesdb.data.clear()
|
|
|
|
with self.subTest('proxy_ipv6'):
|
|
|
|
netvm.features['ipv6'] = True
|
|
|
|
ip6 = qubes.config.qubes_ipv6_prefix.replace(
|
|
|
|
':0000', '') + '::a89:3'
|
2018-04-02 23:59:22 +02:00
|
|
|
expected['/qubes-netvm-gateway6'] = ip6[:-1] + '2'
|
2017-12-14 02:09:31 +01:00
|
|
|
expected['/qubes-firewall/' + ip6] = ''
|
|
|
|
expected['/qubes-firewall/' + ip6 + '/0000'] = 'action=accept'
|
|
|
|
expected['/qubes-firewall/' + ip6 + '/policy'] = 'drop'
|
2020-01-09 16:41:14 +01:00
|
|
|
expected['/connected-ips6'] = ip6
|
|
|
|
|
2017-12-14 02:09:31 +01:00
|
|
|
with unittest.mock.patch('qubes.vm.qubesvm.QubesVM.is_running',
|
|
|
|
lambda _: True):
|
|
|
|
netvm.create_qdb_entries()
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
2019-06-09 23:03:15 +02:00
|
|
|
|
2019-10-20 17:36:06 +02:00
|
|
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
|
|
|
@unittest.mock.patch('qubes.utils.urandom')
|
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
2020-02-27 10:31:27 +01:00
|
|
|
def test_622_qdb_guivm_keyboard_layout(self, mock_qubesdb, mock_urandom,
|
2019-10-20 17:36:06 +02:00
|
|
|
mock_timezone):
|
|
|
|
mock_urandom.return_value = b'A' * 64
|
|
|
|
mock_timezone.return_value = 'UTC'
|
|
|
|
template = self.get_vm(
|
|
|
|
cls=qubes.vm.templatevm.TemplateVM, name='template')
|
|
|
|
template.netvm = None
|
|
|
|
guivm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='sys-gui', qid=2, provides_network=False)
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='appvm', qid=3)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.guivm = guivm
|
2020-06-18 00:31:40 +02:00
|
|
|
vm.is_running = lambda: True
|
|
|
|
guivm.keyboard_layout = 'fr++'
|
2020-02-27 15:01:44 +01:00
|
|
|
guivm.is_running = lambda: True
|
2019-10-20 17:36:06 +02:00
|
|
|
vm.events_enabled = True
|
|
|
|
test_qubesdb = TestQubesDB()
|
|
|
|
mock_qubesdb.write.side_effect = test_qubesdb.write
|
|
|
|
mock_qubesdb.rm.side_effect = test_qubesdb.rm
|
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.maxDiff = None
|
|
|
|
self.assertEqual(test_qubesdb.data, {
|
|
|
|
'/name': 'test-inst-appvm',
|
|
|
|
'/type': 'AppVM',
|
|
|
|
'/default-user': 'user',
|
2020-03-17 15:44:36 +01:00
|
|
|
'/keyboard-layout': 'fr++',
|
2019-10-20 17:36:06 +02:00
|
|
|
'/qubes-vm-type': 'AppVM',
|
|
|
|
'/qubes-gui-domain-xid': '{}'.format(guivm.xid),
|
|
|
|
'/qubes-debug-mode': '0',
|
|
|
|
'/qubes-base-template': 'test-inst-template',
|
|
|
|
'/qubes-timezone': 'UTC',
|
|
|
|
'/qubes-random-seed': base64.b64encode(b'A' * 64),
|
|
|
|
'/qubes-vm-persistence': 'rw-only',
|
|
|
|
'/qubes-vm-updateable': 'False',
|
|
|
|
'/qubes-block-devices': '',
|
|
|
|
'/qubes-usb-devices': '',
|
|
|
|
'/qubes-iptables': 'reload',
|
|
|
|
'/qubes-iptables-error': '',
|
2019-10-22 09:26:03 +02:00
|
|
|
'/qubes-iptables-header': unittest.mock.ANY,
|
2019-10-20 17:36:06 +02:00
|
|
|
'/qubes-service/qubes-update-check': '0',
|
|
|
|
'/qubes-service/meminfo-writer': '1',
|
2020-01-09 16:41:14 +01:00
|
|
|
'/connected-ips': '',
|
|
|
|
'/connected-ips6': '',
|
2019-10-20 17:36:06 +02:00
|
|
|
})
|
|
|
|
|
2020-02-27 10:31:27 +01:00
|
|
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
|
|
|
@unittest.mock.patch('qubes.utils.urandom')
|
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
|
|
|
def test_623_qdb_audiovm(self, mock_qubesdb, mock_urandom,
|
|
|
|
mock_timezone):
|
|
|
|
mock_urandom.return_value = b'A' * 64
|
|
|
|
mock_timezone.return_value = 'UTC'
|
|
|
|
template = self.get_vm(
|
|
|
|
cls=qubes.vm.templatevm.TemplateVM, name='template')
|
|
|
|
template.netvm = None
|
|
|
|
audiovm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='sys-audio', qid=2, provides_network=False)
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='appvm', qid=3)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.audiovm = audiovm
|
2020-06-18 00:31:40 +02:00
|
|
|
vm.is_running = lambda: True
|
2020-02-27 15:01:44 +01:00
|
|
|
audiovm.is_running = lambda: True
|
2020-02-27 10:31:27 +01:00
|
|
|
vm.events_enabled = True
|
|
|
|
test_qubesdb = TestQubesDB()
|
|
|
|
mock_qubesdb.write.side_effect = test_qubesdb.write
|
|
|
|
mock_qubesdb.rm.side_effect = test_qubesdb.rm
|
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.maxDiff = None
|
|
|
|
self.assertEqual(test_qubesdb.data, {
|
|
|
|
'/name': 'test-inst-appvm',
|
|
|
|
'/type': 'AppVM',
|
|
|
|
'/default-user': 'user',
|
|
|
|
'/qubes-vm-type': 'AppVM',
|
|
|
|
'/qubes-audio-domain-xid': '{}'.format(audiovm.xid),
|
|
|
|
'/qubes-debug-mode': '0',
|
|
|
|
'/qubes-base-template': 'test-inst-template',
|
|
|
|
'/qubes-timezone': 'UTC',
|
|
|
|
'/qubes-random-seed': base64.b64encode(b'A' * 64),
|
|
|
|
'/qubes-vm-persistence': 'rw-only',
|
|
|
|
'/qubes-vm-updateable': 'False',
|
|
|
|
'/qubes-block-devices': '',
|
|
|
|
'/qubes-usb-devices': '',
|
|
|
|
'/qubes-iptables': 'reload',
|
|
|
|
'/qubes-iptables-error': '',
|
|
|
|
'/qubes-iptables-header': unittest.mock.ANY,
|
|
|
|
'/qubes-service/qubes-update-check': '0',
|
|
|
|
'/qubes-service/meminfo-writer': '1',
|
2020-02-27 15:01:44 +01:00
|
|
|
'/connected-ips': '',
|
|
|
|
'/connected-ips6': '',
|
2020-02-27 10:31:27 +01:00
|
|
|
})
|
2019-10-20 17:36:06 +02:00
|
|
|
|
2020-03-17 15:44:36 +01:00
|
|
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
|
|
|
@unittest.mock.patch('qubes.utils.urandom')
|
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
|
|
|
def test_624_qdb_guivm_invalid_keyboard_layout(self, mock_qubesdb,
|
|
|
|
mock_urandom, mock_timezone):
|
|
|
|
mock_urandom.return_value = b'A' * 64
|
|
|
|
mock_timezone.return_value = 'UTC'
|
|
|
|
template = self.get_vm(
|
|
|
|
cls=qubes.vm.templatevm.TemplateVM, name='template')
|
|
|
|
guivm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='sys-gui', qid=2, provides_network=False)
|
|
|
|
guivm.is_running = lambda: True
|
|
|
|
guivm.events_enabled = True
|
2020-06-18 00:31:40 +02:00
|
|
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
|
|
|
guivm.keyboard_layout = 'fr123++'
|
|
|
|
|
|
|
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
|
|
|
guivm.keyboard_layout = 'fr+???+'
|
|
|
|
|
|
|
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
|
|
|
guivm.keyboard_layout = 'fr++variant?'
|
|
|
|
|
|
|
|
with self.assertRaises(qubes.exc.QubesPropertyValueError):
|
|
|
|
guivm.keyboard_layout = 'fr'
|
|
|
|
|
|
|
|
@unittest.mock.patch('qubes.utils.get_timezone')
|
|
|
|
@unittest.mock.patch('qubes.utils.urandom')
|
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.untrusted_qdb')
|
|
|
|
def test_625_qdb_keyboard_layout_change(self, mock_qubesdb, mock_urandom,
|
|
|
|
mock_timezone):
|
|
|
|
mock_urandom.return_value = b'A' * 64
|
|
|
|
mock_timezone.return_value = 'UTC'
|
|
|
|
template = self.get_vm(
|
|
|
|
cls=qubes.vm.templatevm.TemplateVM, name='template')
|
|
|
|
template.netvm = None
|
|
|
|
guivm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='sys-gui', qid=2, provides_network=False)
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, template=template,
|
|
|
|
name='appvm', qid=3)
|
|
|
|
vm.netvm = None
|
|
|
|
vm.guivm = guivm
|
|
|
|
vm.is_running = lambda: True
|
|
|
|
guivm.keyboard_layout = 'fr++'
|
|
|
|
guivm.is_running = lambda: True
|
|
|
|
vm.events_enabled = True
|
|
|
|
test_qubesdb = TestQubesDB()
|
|
|
|
mock_qubesdb.write.side_effect = test_qubesdb.write
|
|
|
|
mock_qubesdb.rm.side_effect = test_qubesdb.rm
|
|
|
|
vm.create_qdb_entries()
|
|
|
|
self.maxDiff = None
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'/name': 'test-inst-appvm',
|
|
|
|
'/type': 'AppVM',
|
|
|
|
'/default-user': 'user',
|
|
|
|
'/keyboard-layout': 'fr++',
|
|
|
|
'/qubes-vm-type': 'AppVM',
|
|
|
|
'/qubes-gui-domain-xid': '{}'.format(guivm.xid),
|
|
|
|
'/qubes-debug-mode': '0',
|
|
|
|
'/qubes-base-template': 'test-inst-template',
|
|
|
|
'/qubes-timezone': 'UTC',
|
|
|
|
'/qubes-random-seed': base64.b64encode(b'A' * 64),
|
|
|
|
'/qubes-vm-persistence': 'rw-only',
|
|
|
|
'/qubes-vm-updateable': 'False',
|
|
|
|
'/qubes-block-devices': '',
|
|
|
|
'/qubes-usb-devices': '',
|
|
|
|
'/qubes-iptables': 'reload',
|
|
|
|
'/qubes-iptables-error': '',
|
|
|
|
'/qubes-iptables-header': unittest.mock.ANY,
|
|
|
|
'/qubes-service/qubes-update-check': '0',
|
|
|
|
'/qubes-service/meminfo-writer': '1',
|
|
|
|
'/connected-ips': '',
|
|
|
|
'/connected-ips6': '',
|
|
|
|
}
|
|
|
|
|
|
|
|
with self.subTest('default'):
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
|
|
|
with self.subTest('value_change'):
|
|
|
|
vm.keyboard_layout = 'de++'
|
|
|
|
expected['/keyboard-layout'] = 'de++'
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
|
|
|
with self.subTest('value_revert'):
|
|
|
|
vm.keyboard_layout = qubes.property.DEFAULT
|
|
|
|
expected['/keyboard-layout'] = 'fr++'
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
|
|
|
with self.subTest('no_default'):
|
|
|
|
guivm.keyboard_layout = qubes.property.DEFAULT
|
|
|
|
vm.keyboard_layout = qubes.property.DEFAULT
|
|
|
|
expected['/keyboard-layout'] = 'us++'
|
|
|
|
self.assertEqual(test_qubesdb.data, expected)
|
|
|
|
|
2020-03-17 15:44:36 +01:00
|
|
|
|
2019-06-09 23:03:15 +02:00
|
|
|
@asyncio.coroutine
|
|
|
|
def coroutine_mock(self, mock, *args, **kwargs):
|
|
|
|
return mock(*args, **kwargs)
|
|
|
|
|
|
|
|
@unittest.mock.patch('asyncio.create_subprocess_exec')
|
|
|
|
def test_700_run_service(self, mock_subprocess):
|
|
|
|
func_mock = unittest.mock.Mock()
|
|
|
|
mock_subprocess.side_effect = functools.partial(
|
|
|
|
self.coroutine_mock, func_mock)
|
|
|
|
|
|
|
|
start_mock = unittest.mock.Mock()
|
|
|
|
|
|
|
|
vm = self.get_vm(cls=qubes.vm.standalonevm.StandaloneVM,
|
|
|
|
name='vm')
|
|
|
|
vm.is_running = lambda: True
|
|
|
|
vm.is_qrexec_running = lambda: True
|
|
|
|
vm.start = functools.partial(self.coroutine_mock, start_mock)
|
|
|
|
with self.subTest('running'):
|
|
|
|
self.loop.run_until_complete(vm.run_service('test.service'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'/usr/bin/qrexec-client', '-d', 'test-inst-vm',
|
|
|
|
'user:QUBESRPC test.service dom0')
|
|
|
|
self.assertFalse(start_mock.called)
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
start_mock.reset_mock()
|
|
|
|
with self.subTest('not_running'):
|
|
|
|
vm.is_running = lambda: False
|
|
|
|
with self.assertRaises(qubes.exc.QubesVMNotRunningError):
|
|
|
|
self.loop.run_until_complete(vm.run_service('test.service'))
|
|
|
|
self.assertFalse(func_mock.called)
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
start_mock.reset_mock()
|
|
|
|
with self.subTest('autostart'):
|
|
|
|
vm.is_running = lambda: False
|
|
|
|
self.loop.run_until_complete(vm.run_service(
|
|
|
|
'test.service', autostart=True))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'/usr/bin/qrexec-client', '-d', 'test-inst-vm',
|
|
|
|
'user:QUBESRPC test.service dom0')
|
|
|
|
self.assertTrue(start_mock.called)
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
start_mock.reset_mock()
|
|
|
|
with self.subTest('no_qrexec'):
|
|
|
|
vm.is_running = lambda: True
|
|
|
|
vm.is_qrexec_running = lambda: False
|
|
|
|
with self.assertRaises(qubes.exc.QubesVMError):
|
|
|
|
self.loop.run_until_complete(vm.run_service('test.service'))
|
|
|
|
self.assertFalse(start_mock.called)
|
|
|
|
self.assertFalse(func_mock.called)
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
start_mock.reset_mock()
|
|
|
|
with self.subTest('other_user'):
|
|
|
|
vm.is_running = lambda: True
|
|
|
|
vm.is_qrexec_running = lambda: True
|
|
|
|
self.loop.run_until_complete(vm.run_service('test.service',
|
|
|
|
user='other'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'/usr/bin/qrexec-client', '-d', 'test-inst-vm',
|
|
|
|
'other:QUBESRPC test.service dom0')
|
|
|
|
self.assertFalse(start_mock.called)
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
start_mock.reset_mock()
|
|
|
|
with self.subTest('other_source'):
|
|
|
|
vm.is_running = lambda: True
|
|
|
|
vm.is_qrexec_running = lambda: True
|
|
|
|
self.loop.run_until_complete(vm.run_service('test.service',
|
|
|
|
source='test-inst-vm'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'/usr/bin/qrexec-client', '-d', 'test-inst-vm',
|
|
|
|
'user:QUBESRPC test.service test-inst-vm')
|
|
|
|
self.assertFalse(start_mock.called)
|
|
|
|
|
2020-02-29 23:01:00 +01:00
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.run')
|
|
|
|
def test_710_run_for_stdio(self, mock_run):
|
|
|
|
vm = self.get_vm(cls=qubes.vm.standalonevm.StandaloneVM,
|
|
|
|
name='vm')
|
|
|
|
|
|
|
|
func_mock = unittest.mock.Mock()
|
|
|
|
mock_run.side_effect = functools.partial(
|
|
|
|
self.coroutine_mock, func_mock)
|
|
|
|
communicate_mock = unittest.mock.Mock()
|
|
|
|
func_mock.return_value.communicate.side_effect = functools.partial(
|
|
|
|
self.coroutine_mock, communicate_mock)
|
|
|
|
communicate_mock.return_value = (b'stdout', b'stderr')
|
|
|
|
func_mock.return_value.returncode = 0
|
|
|
|
|
|
|
|
with self.subTest('default'):
|
|
|
|
value = self.loop.run_until_complete(
|
|
|
|
vm.run_for_stdio('cat'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'cat',
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE)
|
|
|
|
communicate_mock.assert_called_once_with(input=b'')
|
|
|
|
self.assertEqual(value, (b'stdout', b'stderr'))
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
communicate_mock.reset_mock()
|
|
|
|
with self.subTest('with_input'):
|
|
|
|
value = self.loop.run_until_complete(
|
|
|
|
vm.run_for_stdio('cat', input=b'abc'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'cat',
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE)
|
|
|
|
communicate_mock.assert_called_once_with(input=b'abc')
|
|
|
|
self.assertEqual(value, (b'stdout', b'stderr'))
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
communicate_mock.reset_mock()
|
|
|
|
with self.subTest('error'):
|
|
|
|
func_mock.return_value.returncode = 1
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as exc:
|
|
|
|
self.loop.run_until_complete(
|
|
|
|
vm.run_for_stdio('cat'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'cat',
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE)
|
|
|
|
communicate_mock.assert_called_once_with(input=b'')
|
|
|
|
self.assertEqual(exc.exception.returncode, 1)
|
|
|
|
self.assertEqual(exc.exception.output, b'stdout')
|
|
|
|
self.assertEqual(exc.exception.stderr, b'stderr')
|
|
|
|
|
2019-06-09 23:03:15 +02:00
|
|
|
@unittest.mock.patch('qubes.vm.qubesvm.QubesVM.run_service')
|
2020-02-29 23:01:00 +01:00
|
|
|
def test_711_run_service_for_stdio(self, mock_run_service):
|
2019-06-09 23:03:15 +02:00
|
|
|
vm = self.get_vm(cls=qubes.vm.standalonevm.StandaloneVM,
|
|
|
|
name='vm')
|
|
|
|
|
|
|
|
func_mock = unittest.mock.Mock()
|
|
|
|
mock_run_service.side_effect = functools.partial(
|
|
|
|
self.coroutine_mock, func_mock)
|
|
|
|
communicate_mock = unittest.mock.Mock()
|
|
|
|
func_mock.return_value.communicate.side_effect = functools.partial(
|
|
|
|
self.coroutine_mock, communicate_mock)
|
|
|
|
communicate_mock.return_value = (b'stdout', b'stderr')
|
|
|
|
func_mock.return_value.returncode = 0
|
|
|
|
|
|
|
|
with self.subTest('default'):
|
|
|
|
value = self.loop.run_until_complete(
|
|
|
|
vm.run_service_for_stdio('test.service'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'test.service',
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE)
|
2020-02-26 05:38:59 +01:00
|
|
|
communicate_mock.assert_called_once_with(input=b'')
|
2019-06-09 23:03:15 +02:00
|
|
|
self.assertEqual(value, (b'stdout', b'stderr'))
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
communicate_mock.reset_mock()
|
|
|
|
with self.subTest('with_input'):
|
|
|
|
value = self.loop.run_until_complete(
|
|
|
|
vm.run_service_for_stdio('test.service', input=b'abc'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'test.service',
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE)
|
|
|
|
communicate_mock.assert_called_once_with(input=b'abc')
|
|
|
|
self.assertEqual(value, (b'stdout', b'stderr'))
|
|
|
|
|
|
|
|
func_mock.reset_mock()
|
|
|
|
communicate_mock.reset_mock()
|
|
|
|
with self.subTest('error'):
|
|
|
|
func_mock.return_value.returncode = 1
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as exc:
|
|
|
|
self.loop.run_until_complete(
|
|
|
|
vm.run_service_for_stdio('test.service'))
|
|
|
|
func_mock.assert_called_once_with(
|
|
|
|
'test.service',
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE)
|
2020-02-26 05:38:59 +01:00
|
|
|
communicate_mock.assert_called_once_with(input=b'')
|
2019-06-09 23:03:15 +02:00
|
|
|
self.assertEqual(exc.exception.returncode, 1)
|
|
|
|
self.assertEqual(exc.exception.output, b'stdout')
|
|
|
|
self.assertEqual(exc.exception.stderr, b'stderr')
|
2020-03-09 05:19:06 +01:00
|
|
|
|
|
|
|
@unittest.mock.patch('os.path.exists')
|
|
|
|
def test_720_is_fully_usable(self, mock_os_path_exists):
|
|
|
|
vm_name = 'workvm'
|
|
|
|
qrexec_file_name = '/var/run/qubes/qrexec.{}'.format(
|
|
|
|
'test-inst-{}'.format(vm_name))
|
|
|
|
vm = self.get_vm(cls=qubes.vm.appvm.AppVM, name=vm_name)
|
|
|
|
|
|
|
|
# Dummy xid; greater than 0 to indicate a running AppVM
|
|
|
|
vm._qubesprop_xid = 10
|
|
|
|
self.assertGreater(vm.xid, 0)
|
|
|
|
|
|
|
|
with self.subTest('with_qrexec_started'):
|
|
|
|
mock_os_path_exists.return_value = True
|
|
|
|
vm.features['qrexec'] = True
|
|
|
|
|
|
|
|
fully_usable = vm.is_fully_usable()
|
|
|
|
mock_os_path_exists.assert_called_once_with(qrexec_file_name)
|
|
|
|
self.assertEqual(fully_usable, True)
|
|
|
|
|
|
|
|
mock_os_path_exists.reset_mock()
|
|
|
|
with self.subTest('with_qrexec_error'):
|
|
|
|
mock_os_path_exists.return_value = False
|
|
|
|
vm.features['qrexec'] = True
|
|
|
|
|
|
|
|
fully_usable = vm.is_fully_usable()
|
|
|
|
mock_os_path_exists.assert_called_once_with(qrexec_file_name)
|
|
|
|
self.assertEqual(fully_usable, False)
|
|
|
|
|
|
|
|
mock_os_path_exists.reset_mock()
|
|
|
|
with self.subTest('without_qrexec'):
|
|
|
|
vm.features['qrexec'] = False
|
|
|
|
|
|
|
|
fully_usable = vm.is_fully_usable()
|
|
|
|
mock_os_path_exists.assert_not_called()
|
|
|
|
self.assertEqual(fully_usable, True)
|