Browse Source

Merge remote-tracking branch 'origin/pr/281'

* origin/pr/281:
  Tests suite fixes
  Use internal vm.shutdown_timeout
  Fix pylint warning
  Show all non halted vm's as running
  use force=True when restarting a netvm with connected vms
  Join all connected VM's in same warning
  Rewrite without cascade var
  Don't wait!
  First initiate shutdown for all vm's, then wait
  Fix wrong identation (thanks pylint)
  Avoid infite loop while waiting vm to shutdown
  Fix pylint warnings
  Cascade shutdown
Marek Marczykowski-Górecki 2 years ago
parent
commit
aaf99091ef
2 changed files with 53 additions and 26 deletions
  1. 41 15
      qubesmanager/qube_manager.py
  2. 12 11
      qubesmanager/tests/test_qube_manager.py

+ 41 - 15
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 <b>'{0}'</b> hasn't shutdown within the last "
                         "{1} seconds, do you want to kill it?<br>").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 <b>'{0}'</b>!"
+                        "<br><small>Do you want to shutdown: </small>"
+                        "<b>'{1}'</b>?").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:

+ 12 - 11
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()