Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions doc/manpages/qvm-features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,10 @@ on it.
preload-dispvm-max
^^^^^^^^^^^^^^^^^^

Number of disposables to preload. Upon setting, the number of running preloaded
disposables will be adjusted to match the maximum configured, if there is not
enough of them and there is enough available memory on the system, new ones will
be created, if there are more than enough, the excess will be removed.
Number of disposables to preload. Upon setting, the quantity of running
preloaded disposables will be adjusted to match the maximum configured, if there
is not enough of them and there is enough available memory on the system, new
ones will be created, if there are more than enough, the excess will be removed.

|
| **Valid on**: disposable template
Expand All @@ -455,12 +455,6 @@ disposable in the list. As soon as the preloaded disposable is requested to be
used, it is removed from the `preload-dispvm` list, GUI applications entries
become visible, followed by a new disposable being preloaded.

.. warning::

Applications configured to autostart by the disposable template or the
template itself will be interactive before the preloaded disposable can be
paused.

|
| **Managed by**: system
| **Valid on**: disposable template
Expand Down
11 changes: 9 additions & 2 deletions qubesadmin/tests/mock_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,12 +986,19 @@ def __init__(self):
self._qubes["default-dvm"] = MockQube(
name="default-dvm",
qapp=self,
klass="DispVM",
template_for_dispvms="True",
klass="AppVM",
template_for_dispvms=True,
template="fedora-36",
features={"appmenus-dispvm": "1"},
)

self._qubes["test-disp"] = MockQube(
name="test-disp",
qapp=self,
klass="DispVM",
template="default-dvm",
)

self._qubes["test-vm"] = MockQube(
name="test-vm",
qapp=self,
Expand Down
162 changes: 162 additions & 0 deletions qubesadmin/tests/tools/qvm_start_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ def test_020_start_gui_for_vm(self, proc_mock):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'virt_mode', None)] = \
b'0\x00default=False type=str pv'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
self.app.expected_calls[
('test-vm', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
Expand Down Expand Up @@ -272,6 +276,10 @@ def test_021_start_gui_for_vm_hvm(self, proc_mock):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
b'0\x00default=False type=bool False'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
self.app.expected_calls[
('test-vm', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
Expand Down Expand Up @@ -312,6 +320,10 @@ def test_022_start_gui_for_vm_hvm_stubdom(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
b'0\x00default=False type=bool False'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
self.app.expected_calls[
('test-vm', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
Expand Down Expand Up @@ -360,6 +372,10 @@ def test_030_start_gui_for_stubdomain(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
b'0\x00default=False type=int 3001'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
self.app.expected_calls[
('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
Expand Down Expand Up @@ -391,6 +407,10 @@ def test_031_start_gui_for_stubdomain_forced(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
b'0\x00default=False type=int 3001'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
# self.app.expected_calls[
# ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
# b'0\x00'
Expand All @@ -411,6 +431,128 @@ def test_031_start_gui_for_stubdomain_forced(self):
async def mock_coroutine(self, mock, *args, **kwargs):
mock(*args, **kwargs)

def test_038_start_gui_skip_preload(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self.addCleanup(loop.close)

self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00test-disp class=DispVM state=Running\n' \
b'gui-vm class=AppVM state=Running'
self.app.expected_calls[
('test-disp', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running'
self.app.expected_calls[
('test-disp', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
b'0\x00True'
self.app.expected_calls[
('test-disp', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'virt_mode', None)] = \
b'0\x00default=False type=str hvm'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'xid', None)] = \
b'0\x00default=False type=int 3000'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
b'0\x00default=False type=int 3001'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'guivm', None)] = \
b'0\x00default=False type=vm gui-vm'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Set', 'is_preload', b'True')] = \
b'0\x00default=False type=vm gui-vm'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'is_preload', None)] = \
b'0\x00default=False type=bool True'

# pylint: disable=protected-access
self.app._local_name = 'gui-vm'
vm = self.app.domains['test-disp']
vm.is_preload = True
mock_start_vm = unittest.mock.Mock()
mock_start_stubdomain = unittest.mock.Mock()
patch_start_vm = unittest.mock.patch.object(
self.launcher, 'start_gui_for_vm', functools.partial(
self.mock_coroutine, mock_start_vm))
patch_start_stubdomain = unittest.mock.patch.object(
self.launcher, 'start_gui_for_stubdomain', lambda vm_, force:
self.mock_coroutine(mock_start_stubdomain, vm_))
try:
patch_start_vm.start()
patch_start_stubdomain.start()
loop.run_until_complete(self.launcher.start_gui(vm))
mock_start_vm.assert_not_called()
mock_start_stubdomain.assert_not_called()
finally:
unittest.mock.patch.stopall()

def test_039_start_gui_for_preload(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self.addCleanup(loop.close)

self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00test-disp class=DispVM state=Running\n' \
b'gui-vm class=AppVM state=Running'
self.app.expected_calls[
('test-disp', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running'
self.app.expected_calls[
('test-disp', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
b'0\x00True'
self.app.expected_calls[
('test-disp', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'virt_mode', None)] = \
b'0\x00default=False type=str hvm'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'xid', None)] = \
b'0\x00default=False type=int 3000'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
b'0\x00default=False type=int 3001'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'guivm', None)] = \
b'0\x00default=False type=vm gui-vm'
self.app.expected_calls[
('test-disp', 'admin.vm.property.Get', 'is_preload', None)] = \
b"2\x00QubesNoSuchPropertyError\x00\x00Invalid property " \
b"'is_preload' of test-disp\x00"

# pylint: disable=protected-access
self.app._local_name = 'gui-vm'
vm = self.app.domains['test-disp']

with \
unittest.mock.patch('asyncio.ensure_future'), \
unittest.mock.patch.object(
self.launcher, 'common_guid_args', lambda vm: []
), \
unittest.mock.patch.object(
qubesadmin.tools.qvm_start_daemon,
'get_monitor_layout',
unittest.mock.Mock(return_value=None)
), \
unittest.mock.patch.object(
self.launcher, 'start_gui', unittest.mock.Mock()
) as mock_start, \
unittest.mock.patch.object(
self.launcher, 'start_audio', unittest.mock.Mock()
) as mock_start_audio:
# Execute and validate
self.launcher.on_property_preload_set(
vm, 'property-reset:is_preload'
)
mock_start_audio.assert_called_once_with(vm)
mock_start.assert_called_once_with(vm, monitor_layout=None)

def test_040_start_gui(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
Expand Down Expand Up @@ -442,6 +584,10 @@ def test_040_start_gui(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'guivm', None)] = \
b'0\x00default=False type=vm gui-vm'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"

# pylint: disable=protected-access
self.app._local_name = 'gui-vm'
Expand Down Expand Up @@ -562,6 +708,10 @@ def test_060_send_monitor_layout(self):
('test-vm', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"

vm = self.app.domains['test-vm']
mock_run_service = unittest.mock.Mock(spec={})
Expand Down Expand Up @@ -649,6 +799,10 @@ def test_063_send_monitor_layout_signal_existing(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
b'0\x00default=False type=int 124'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
self.app.expected_calls[
('test-vm', 'admin.vm.feature.CheckWithTemplate',
'no-monitor-layout', None)] = \
Expand Down Expand Up @@ -691,6 +845,14 @@ def test_070_send_monitor_layout_all(self):
b'test-vm3 class=AppVM state=Running\n' \
b'test-vm4 class=AppVM state=Halted\n' \
b'gui-vm class=AppVM state=Running'
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm\x00"
self.app.expected_calls[
('test-vm2', 'admin.vm.property.Get', 'is_preload', None)] = \
b'2\x00QubesNoSuchPropertyError\x00\x00Invalid property ' \
b"'is_preload' of test-vm2\x00"
self.app.expected_calls[
('test-vm', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running'
Expand Down
Loading