diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index e9e61df..bde0775 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -493,17 +493,15 @@ class QubesTableModel(QAbstractTableModel): return def_flags | Qt.ItemIsUserCheckable return def_flags -vm_shutdown_timeout = 20000 # in msec vm_restart_check_timeout = 1000 # in msec class VmShutdownMonitor(QObject): - def __init__(self, vm, shutdown_time=vm_shutdown_timeout, - check_time=vm_restart_check_timeout, + def __init__(self, vm, check_time=vm_restart_check_timeout, and_restart=False, caller=None): QObject.__init__(self) self.vm = vm - self.shutdown_time = shutdown_time + self.shutdown_timeout = vm.shutdown_timeout self.check_time = check_time self.and_restart = and_restart self.shutdown_started = datetime.now() @@ -519,7 +517,7 @@ class VmShutdownMonitor(QObject): def timeout_reached(self): actual = datetime.now() - self.shutdown_started - allowed = timedelta(milliseconds=self.shutdown_time) + allowed = timedelta(seconds=self.shutdown_timeout) return actual > allowed @@ -541,12 +539,12 @@ class VmShutdownMonitor(QObject): msgbox.setText(self.tr( "The Qube '{0}' hasn't shutdown within the last " "{1} seconds, do you want to kill it?
").format( - vm.name, self.shutdown_time / 1000)) + vm.name, self.shutdown_timeout)) kill_button = msgbox.addButton( self.tr("Kill it!"), QMessageBox.YesRole) wait_button = msgbox.addButton( self.tr("Wait another {0} seconds...").format( - self.shutdown_time / 1000), + self.shutdown_timeout), QMessageBox.NoRole) ignore_button = msgbox.addButton(self.tr("Don't ask again"), QMessageBox.RejectRole) @@ -668,7 +666,7 @@ class QubesProxyModel(QSortFilterProxyModel): vm = self.sourceModel().data(index, Qt.UserRole) if self.window.show_running.isChecked() and \ - vm.state['power'] == 'Running': + vm.state['power'] != 'Halted': return super().filterAcceptsRow(sourceRow, sourceParent) if self.window.show_halted.isChecked() and \ vm.state['power'] == 'Halted': @@ -1407,24 +1405,52 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): if reply == QMessageBox.Yes: self.shutdown_vm(vm) - def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout, - check_time=vm_restart_check_timeout, and_restart=False): + def get_connected_vms(self, vm, connected_vms): + for connected_vm in vm.connected_vms: + if connected_vm.is_running(): + connected_vms.append(connected_vm) + self.get_connected_vms(connected_vm, connected_vms) + + def shutdown_vm(self, vm, force=False, check_time=vm_restart_check_timeout, + and_restart=False): try: - vm.shutdown() + connected_vms = [] + + if not and_restart: + self.get_connected_vms(vm, connected_vms) + + if len(connected_vms) > 0: + reply = QMessageBox.question( + self, self.tr("Qube Shutdown Confirmation"), + self.tr("There are some qubes connected to '{0}'!" + "
Do you want to shutdown: " + "'{1}'?").format(vm.name, + ", ".join([x.name for x in connected_vms])), + QMessageBox.Yes | QMessageBox.Cancel) + + if reply != QMessageBox.Yes: + return False + + force = True + for connected_vm in connected_vms: + connected_vm.shutdown(force=force) + + vm.shutdown(force=force) except exc.QubesException as ex: QMessageBox.warning( self, self.tr("Error shutting down Qube!"), self.tr("ERROR: {0}").format(ex)) - return + return False - self.shutdown_monitor[vm.qid] = VmShutdownMonitor(vm, shutdown_time, - check_time, + self.shutdown_monitor[vm.qid] = VmShutdownMonitor(vm, check_time, and_restart, self) # noinspection PyCallByClass,PyTypeChecker QTimer.singleShot(check_time, self.shutdown_monitor[ vm.qid].check_if_vm_has_shutdown) + return True + # noinspection PyArgumentList @pyqtSlot(name='on_action_restartvm_triggered') def action_restartvm_triggered(self): @@ -1441,7 +1467,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow): # in case the user shut down the VM in the meantime try: if manager_utils.is_running(vm, False): - self.shutdown_vm(vm, and_restart=True) + self.shutdown_vm(vm, force=True, and_restart=True) else: self.start_vm(vm) except exc.QubesException as ex: diff --git a/qubesmanager/tests/test_qube_manager.py b/qubesmanager/tests/test_qube_manager.py index 6efe283..d126535 100644 --- a/qubesmanager/tests/test_qube_manager.py +++ b/qubesmanager/tests/test_qube_manager.py @@ -457,11 +457,10 @@ class QubeManagerTest(unittest.TestCase): with unittest.mock.patch.object(selected_vm, 'shutdown')\ as mock_shutdown: self.dialog.action_shutdownvm.trigger() - mock_shutdown.assert_called_once_with() + mock_shutdown.assert_called_once_with(force=False) mock_monitor.assert_called_once_with( - selected_vm, - unittest.mock.ANY, unittest.mock.ANY, - unittest.mock.ANY, unittest.mock.ANY) + selected_vm, unittest.mock.ANY, unittest.mock.ANY, + unittest.mock.ANY) mock_timer.assert_called_once_with(unittest.mock.ANY, unittest.mock.ANY) @@ -546,10 +545,9 @@ class QubeManagerTest(unittest.TestCase): with unittest.mock.patch.object(selected_vm, 'shutdown')\ as mock_shutdown: action.trigger() - mock_shutdown.assert_called_once_with() + mock_shutdown.assert_called_once_with(force=True) mock_monitor.assert_called_once_with( - selected_vm, unittest.mock.ANY, - unittest.mock.ANY, True, unittest.mock.ANY) + selected_vm, 1000, True, unittest.mock.ANY) @unittest.mock.patch('qubesmanager.qube_manager.StartVMThread') @unittest.mock.patch("PyQt5.QtWidgets.QMessageBox.question", @@ -1484,7 +1482,7 @@ class QubeManagerTest(unittest.TestCase): for row in range(self.dialog.table.model().rowCount()): template = self._get_table_item(row, "Template") vm = self._get_table_vm(row) - if template != 'AdminVM' and \ + if template != 'AdminVM' and not vm.provides_network and \ (running is None or (running and vm.is_running()) or (not running and not vm.is_running())): @@ -1691,8 +1689,9 @@ class VMShutdownMonitorTest(unittest.TestCase): mock_vm = unittest.mock.Mock() mock_vm.is_running.return_value = True mock_vm.start_time = datetime.datetime.now().timestamp() - 3000 + mock_vm.shutdown_timeout = 60 - monitor = qube_manager.VmShutdownMonitor(mock_vm, shutdown_time=1) + monitor = qube_manager.VmShutdownMonitor(mock_vm) time.sleep(3) monitor.check_if_vm_has_shutdown() @@ -1708,8 +1707,9 @@ class VMShutdownMonitorTest(unittest.TestCase): mock_vm = unittest.mock.Mock() mock_vm.is_running.return_value = True mock_vm.start_time = datetime.datetime.now().timestamp() - 3000 + mock_vm.shutdown_timeout = 1 - monitor = qube_manager.VmShutdownMonitor(mock_vm, shutdown_time=1) + monitor = qube_manager.VmShutdownMonitor(mock_vm) time.sleep(3) monitor.restart_vm_if_needed = unittest.mock.Mock() @@ -1725,8 +1725,9 @@ class VMShutdownMonitorTest(unittest.TestCase): mock_vm = unittest.mock.Mock() mock_vm.is_running.return_value = True mock_vm.start_time = datetime.datetime.now().timestamp() - 3000 + mock_vm.shutdown_timeout = 30 - monitor = qube_manager.VmShutdownMonitor(mock_vm, shutdown_time=3000) + monitor = qube_manager.VmShutdownMonitor(mock_vm) time.sleep(1) monitor.check_if_vm_has_shutdown()