diff --git a/.pylintrc b/.pylintrc index bd057eec..b438d897 100644 --- a/.pylintrc +++ b/.pylintrc @@ -57,10 +57,10 @@ const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$ class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$ # Regular expression which should only match correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ +function-rgx=[a-z_][a-z0-9_]{2,40}$ # Regular expression which should only match correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ +method-rgx=[a-z_][a-z0-9_]{2,40}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ diff --git a/debian/control b/debian/control index d3d1cd66..d8b13042 100644 --- a/debian/control +++ b/debian/control @@ -22,7 +22,7 @@ X-Python3-Version: >= 3.4 Package: qubes-manager Architecture: any Depends: - python3-qubesadmin (>= 4.3.13), + python3-qubesadmin (>= 4.3.33), python3-qubesimgconverter, python3-pyqt6, python3-pyinotify, diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py index 8f4c926b..17de964b 100644 --- a/qubesmanager/bootfromdevice.py +++ b/qubesmanager/bootfromdevice.py @@ -24,7 +24,7 @@ from PyQt6 import QtWidgets, QtGui, QtCore # pylint: disable=import-error from qubesadmin import tools from qubesadmin import exc -from qubesadmin.tools import qvm_start +from qubesadmin import utils as admin_utils # this is needed for icons to actually work # pylint: disable=unused-import, no-name-in-module @@ -178,7 +178,7 @@ def main(args=None): window = utils.run_synchronous( functools.partial(VMBootFromDeviceWindow, vm)) if window.result() == 1 and window.cdrom_location is not None: - qvm_start.main(['--cdrom', window.cdrom_location, vm.name]) + admin_utils.start_expert(domain=vm, drive="cdrom:" + window.cdrom_location) if __name__ == "__main__": main() diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 2fa1bb90..6f6164aa 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -31,7 +31,6 @@ from qubesadmin import exc from qubesadmin import utils -from qubesadmin.tools import qvm_start # pylint: disable=import-error from PyQt6 import QtWidgets @@ -696,6 +695,20 @@ def run(self): self.msg = ("Error starting Qube!", str(ex)) +# pylint: disable=too-few-public-methods +class ShutdownVMThread(common_threads.QubesThread): + def __init__(self, vm, force: bool = False, wait: bool = False): + super().__init__(vm) + self.force = force + self.wait_end = wait # wait() is callable, let's not interfere. + + def run(self): + try: + self.vm.shutdown(force=self.force, wait=self.wait_end) + except exc.QubesException as ex: + self.msg = ("Error starting Qube!", str(ex)) + + # pylint: disable=too-few-public-methods class UpdateVMsThread(common_threads.QubesThread): def run(self): @@ -1599,8 +1612,9 @@ def action_startvm_tools_install_triggered(self): self.tr("'qubes-windows-tools' is not installed in dom0.")) for vm_info in self.get_selected_vms(): vm = vm_info.vm - qvm_start.main(['--cdrom', - 'dom0:/usr/lib/qubes/qubes-windows-tools.iso', vm.name]) + utils.start_expert( + domain=vm, drive="cdrom:dom0:/usr/lib/qubes/qubes-windows-tools.iso" + ) @pyqtSlot(name='on_action_pausevm_triggered') def action_pausevm_triggered(self): @@ -1660,9 +1674,15 @@ def shutdown_vm(self, vm, force=False, check_time=vm_restart_check_timeout, force = True for connected_vm in connected_vms: - connected_vm.shutdown(force=force) + thread = ShutdownVMThread(connected_vm, force=force) + self.threads_list.append(thread) + thread.finished.connect(self.clear_threads) + thread.start() - vm.shutdown(force=force) + thread = ShutdownVMThread(vm, force=force, wait=True) + self.threads_list.append(thread) + thread.finished.connect(self.clear_threads) + thread.start() except exc.QubesException as ex: QMessageBox.warning( self, diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 6ea6ed2c..39f7b7ee 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -34,7 +34,6 @@ from qubesadmin import device_protocol from qubesadmin.device_protocol import DeviceCategory from qubesadmin import utils as admin_utils -from qubesadmin.tools import qvm_start import qubesadmin.exc from . import bootfromdevice @@ -1393,7 +1392,9 @@ def boot_from_cdrom_button_pressed(self): ) if boot_dialog.exec(): self.save_and_apply() - qvm_start.main(["--cdrom", boot_dialog.cdrom_location, self.vm.name]) + admin_utils.start_expert( + domain=self.vm, drive="cdrom:" + boot_dialog.cdrom_location + ) def virt_mode_changed(self, new_idx): # pylint: disable=unused-argument self.update_pv_warning() diff --git a/qubesmanager/tests/test_qube_manager.py b/qubesmanager/tests/test_qube_manager.py index 3ebac4cc..09632868 100644 --- a/qubesmanager/tests/test_qube_manager.py +++ b/qubesmanager/tests/test_qube_manager.py @@ -441,11 +441,13 @@ def test_209_shutdownvm(mock_monitor, mock_timer, _mock_question, assert qubes_manager.action_shutdownvm.isEnabled() vm = qubes_manager.qubes_app.domains['test-blue'] - with mock.patch.object(vm, 'shutdown') as mock_shutdown: - qubes_manager.action_shutdownvm.trigger() - mock_shutdown.assert_called_once_with(force=False) - mock_monitor.assert_called_once_with(vm, mock.ANY, mock.ANY, mock.ANY) - mock_timer.assert_called_once_with(mock.ANY, mock.ANY) + expected_shutdown = ('test-blue', 'admin.vm.Shutdown', 'wait', None) + assert expected_shutdown not in qubes_manager.qubes_app.actual_calls + qubes_manager.qubes_app.expected_calls[expected_shutdown] = b'0\x00' + + qubes_manager.action_shutdownvm.trigger() + mock_monitor.assert_called_once_with(vm, mock.ANY, mock.ANY, mock.ANY) + mock_timer.assert_called_once_with(mock.ANY, mock.ANY) def test_211_remove_adminvm(qubes_manager): @@ -508,10 +510,12 @@ def test_214_restartvm(_msgbox, mock_monitor, _qtimer, qubes_manager): assert qubes_manager.action_restartvm.isEnabled() vm = qubes_manager.qubes_app.domains['test-blue'] - with mock.patch.object(vm, 'shutdown') as mock_shutdown: - qubes_manager.action_restartvm.trigger() - mock_shutdown.assert_called_once_with(force=True) - mock_monitor.assert_called_once_with(vm, 1000, True, mock.ANY) + expected_shutdown = ('test-blue', 'admin.vm.Shutdown', 'force+wait', None) + assert expected_shutdown not in qubes_manager.qubes_app.actual_calls + qubes_manager.qubes_app.expected_calls[expected_shutdown] = b'0\x00' + + qubes_manager.action_restartvm.trigger() + mock_monitor.assert_called_once_with(vm, 1000, True, mock.ANY) @mock.patch('qubesmanager.qube_manager.UpdateVMsThread') @@ -1450,6 +1454,30 @@ def test_602_startvm_thread_error(): assert thread.msg is not None +def test_603_shutdownvm_thread(): + vm = mock.Mock(spec=['shutdown']) + + thread = qube_manager.ShutdownVMThread(vm) + thread.run() + vm.shutdown.assert_called_once_with(force=False, wait=False) + vm.shutdown.reset_mock() + + thread = qube_manager.ShutdownVMThread(vm, force=True, wait=True) + thread.run() + vm.shutdown.assert_called_once_with(force=True, wait=True) + + +def test_604_shutdownvm_thread_error(): + vm = mock.Mock( + spec=['shutdown'], + **{'shutdown.side_effect': exc.QubesException('Error')}) + + thread = qube_manager.ShutdownVMThread(vm) + thread.run() + + assert thread.msg is not None + + def test_610_run_command_thread(): vm = mock.Mock(spec=['run']) diff --git a/qubesmanager/tests/test_vm_settings.py b/qubesmanager/tests/test_vm_settings.py index 64ae8f0b..1906f5f5 100644 --- a/qubesmanager/tests/test_vm_settings.py +++ b/qubesmanager/tests/test_vm_settings.py @@ -1101,7 +1101,7 @@ def test_211_virtmode(settings_fixture): assert expected_call in settings_window.qubesapp.actual_calls -@mock.patch("qubesadmin.tools.qvm_start.main") +@mock.patch("qubesadmin.utils.start_expert") @mock.patch("qubesmanager.bootfromdevice.VMBootFromDeviceWindow") @check_errors @pytest.mark.parametrize("settings_fixture", TEST_VMS, indirect=True) @@ -1120,7 +1120,7 @@ def test_212_boot_from_device(mock_boot, mock_start, settings_fixture): parent=settings_window, ) - mock_start.assert_called_with(["--cdrom", mock.ANY, vm.name]) + mock_start.assert_called_with(domain=vm, drive=mock.ANY) @check_errors diff --git a/rpm_spec/qmgr.spec.in b/rpm_spec/qmgr.spec.in index dfae655c..753ba7e0 100644 --- a/rpm_spec/qmgr.spec.in +++ b/rpm_spec/qmgr.spec.in @@ -10,7 +10,7 @@ URL: https://www.qubes-os.org Requires: python%{python3_pkgversion} Requires: python%{python3_pkgversion}-pyqt6 Requires: python%{python3_pkgversion}-inotify -Requires: python%{python3_pkgversion}-qubesadmin >= 4.3.13 +Requires: python%{python3_pkgversion}-qubesadmin >= 4.3.33 Requires: python%{python3_pkgversion}-qubesimgconverter Requires: python%{python3_pkgversion}-qasync Requires: python%{python3_pkgversion}-pyxdg