Преглед на файлове

Handling VM dependencies in GUI tools

Added the following reactions ot VM dependencies in GUI tools:
- Qube Manager will inform the user why they cannot delete a VM (which
properties of which VMS [or global] are using the VM)
- Settings window will try to intelligently rename VMs (change
properties to the new name, if possible, and inform the user what went
wrong if not)
- Settings window will inform the user why they cannot delete a VM
Also, renaming VM from Settings launched from Qube Manager will
refresh the VM list through a horrible hack, to be replaced by a neater
Admin API solution in some near future.

depends on https://github.com/QubesOS/qubes-core-admin-client/pull/73/commits/ca848ca7bd07dfb6c2135c4767b0e6536cd9a430

fixes QubesOS/qubes-issues#3992
fixes QubesOS/qubes-issues#3993
fixes QubesOS/qubes-issues#3892
fixes QubesOS/qubes-issues#3499
Christopher Laprise преди 5 години
родител
ревизия
b2fdbb950a
променени са 3 файла, в които са добавени 108 реда и са изтрити 19 реда
  1. 30 15
      qubesmanager/qube_manager.py
  2. 63 4
      qubesmanager/settings.py
  3. 15 0
      qubesmanager/utils.py

+ 30 - 15
qubesmanager/qube_manager.py

@@ -50,6 +50,7 @@ from . import global_settings
 from . import restore
 from . import backup
 from . import log_dialog
+from . import utils as manager_utils
 
 
 class SearchBox(QtGui.QLineEdit):
@@ -413,7 +414,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
     def check_updates(self):
         for vm in self.qubes_app.domains:
             if vm.klass in {'TemplateVM', 'StandaloneVM'}:
-                self.vms_in_table[vm.qid].update()
+                try:
+                    self.vms_in_table[vm.qid].update()
+                except exc.QubesException:
+                    # the VM might have vanished in the meantime
+                    pass
 
     def on_domain_added(self, _, domain):
         #needs to clear cache
@@ -686,26 +691,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
 
         vm = self.get_selected_vm()
 
-        dependencies = utils.vm_usage(self.qubes_app, vm)
+        dependencies = utils.vm_dependencies(self.qubes_app, vm)
 
         if dependencies:
-            list_text = "<br>"
-            for (holder, prop) in dependencies:
-                if holder is None:
-                    list_text += "- Global property <b>{}</b> <br>".format(prop)
-                else:
-                    list_text += "- <b>{}</b> for qube <b>{}</b> <br>".format(
-                        prop, holder.name)
-            list_text += "<br>"
+            list_text = "<br>" + \
+                        manager_utils.format_dependencies_list(dependencies) + \
+                        "<br>"
 
             info_dialog = QtGui.QMessageBox(self)
             info_dialog.setWindowTitle(self.tr("Warning!"))
             info_dialog.setText(
                 self.tr("This qube cannot be removed. It is used as:"
-                        " <br>" + list_text + "<small>If you want to "
-                        " remove this qube, you should remove or change "
-                        " settings of each qube or setting that uses"
-                                              " it.</small>"))
+                        " <br> {} <small>If you want to  remove this qube, "
+                        "you should remove or change settings of each qube "
+                        "or setting that uses it.</small>".format(list_text)))
             info_dialog.setModal(False)
             info_dialog.show()
             self.qt_app.processEvents()
@@ -1001,8 +1000,24 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
             settings_window = settings.VMSettingsWindow(
                 vm, self.qt_app, "basic")
             settings_window.exec_()
-            self.vms_in_table[vm.qid].update()
 
+            vm_deleted = False
+
+            try:
+                # the VM might not exist after running Settings - it might
+                # have been cloned or removed
+                self.vms_in_table[vm.qid].update()
+            except exc.QubesException:
+                # TODO: this will be replaced by proper signal handling once
+                # settings are migrated to AdminAPI
+                vm_deleted = True
+
+            if vm_deleted:
+                for row in self.vms_in_table:
+                    try:
+                        self.vms_in_table[row].update()
+                    except exc.QubesException:
+                        pass
 
     # noinspection PyArgumentList
     @QtCore.pyqtSlot(name='on_action_appmenus_triggered')

+ 63 - 4
qubesmanager/settings.py

@@ -34,6 +34,7 @@ import traceback
 import sys
 from qubesadmin.tools import QubesArgumentParser
 from qubesadmin import devices
+from qubesadmin import utils as admin_utils
 import qubesadmin.exc
 
 from . import utils
@@ -481,10 +482,35 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
             return False
         return True
 
-    def _rename_vm(self, t_monitor, name):
+    def _rename_vm(self, t_monitor, name, dependencies):
         try:
-            self.vm.app.clone_vm(self.vm, name)
-            del self.vm.app.domains[self.vm.name]
+            new_vm = self.vm.app.clone_vm(self.vm, name)
+
+            failed_props = []
+
+            for (holder, prop) in dependencies:
+                try:
+                    if holder is None:
+                        setattr(self.vm.app, prop, new_vm)
+                    else:
+                        setattr(holder, prop, new_vm)
+                except qubesadmin.exc.QubesException as qex:
+                    failed_props += (holder, prop)
+
+            if not failed_props:
+                del self.vm.app.domains[self.vm.name]
+            else:
+                list_text = utils.format_dependencies_list(failed_props)
+
+                QtGui.QMessageBox.warning(
+                    self,
+                    self.tr("Warning: rename partially unsuccessful"),
+                    self.tr("Some properties could not be changed to the new "
+                            "name. The system has now both {} and {} qubes. "
+                            "To resolve this, please check and change the "
+                            "following properties and remove the qube {} "
+                            "manually.<br> ".format(
+                                self.vm.name, name, self.vm.name) + list_text))
 
         except qubesadmin.exc.QubesException as qex:
             t_monitor.set_error_msg(str(qex))
@@ -494,13 +520,31 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
         t_monitor.set_finished()
 
     def rename_vm(self):
+
+        dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
+
+        running_dependencies = [vm.name for (vm, prop) in dependencies
+                                if vm and prop == 'template'
+                                and vm.is_running()]
+
+        if running_dependencies:
+            QtGui.QMessageBox.warning(
+                self,
+                self.tr("Qube cannot be renamed!"),
+                self.tr(
+                    "The following qubes using this qube as a template are "
+                    "running: <br> {}. <br> In order to rename this qube, you "
+                    "must first shut them down.".format(
+                        ", ".join(running_dependencies))))
+            return
+
         new_vm_name, ok = QtGui.QInputDialog.getText(
             self,
             self.tr('Rename qube'),
             self.tr('New name: (WARNING: all other changes will be discarded)'))
 
         if ok:
-            if self._run_in_thread(self._rename_vm, new_vm_name):
+            if self._run_in_thread(self._rename_vm, new_vm_name, dependencies):
                 self.done(0)
 
     def _remove_vm(self, t_monitor):
@@ -516,6 +560,21 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
 
     def remove_vm(self):
 
+        dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
+
+        if dependencies:
+            list_text = utils.format_dependencies_list(dependencies)
+            QtGui.QMessageBox.warning(
+                self,
+                self.tr("Qube cannot be removed!"),
+                self.tr("This qube cannot be removed. It is used as:"
+                        " <br> {} <small>If you want to  remove this qube, "
+                        "you should remove or change settings of each qube "
+                        "or setting that uses it.</small>".format(list_text)))
+
+            return
+
+
         answer, ok = QtGui.QInputDialog.getText(
             self,
             self.tr('Delete qube'),

+ 15 - 0
qubesmanager/utils.py

@@ -172,3 +172,18 @@ def get_path_from_vm(vm, service_name):
         assert '\0' not in untrusted_path
         return untrusted_path.strip()
     raise ValueError('Unexpected characters in path.')
+
+
+def format_dependencies_list(dependencies):
+    """Given a list of tuples representing properties, formats them in
+    a readable list."""
+
+    list_text = ""
+    for (holder, prop) in dependencies:
+        if holder is None:
+            list_text += "- Global property <b>{}</b> <br>".format(prop)
+        else:
+            list_text += "- <b>{}</b> for qube <b>{}</b> <br>".format(
+                prop, holder.name)
+
+    return list_text