Răsfoiți Sursa

Merge remote-tracking branch 'qubesos/pr/120'

Marek Marczykowski-Górecki 5 ani în urmă
părinte
comite
ee9862d010

+ 43 - 17
qubesmanager/qube_manager.py

@@ -35,6 +35,7 @@ from pydbus import SessionBus
 
 from qubesadmin import Qubes
 from qubesadmin import exc
+from qubesadmin import utils
 
 from PyQt4 import QtGui  # pylint: disable=import-error
 from PyQt4 import QtCore  # pylint: disable=import-error
@@ -49,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):
@@ -412,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
@@ -685,21 +691,25 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
 
         vm = self.get_selected_vm()
 
-        if vm.klass == 'TemplateVM':
-            dependent_vms = 0
-            for single_vm in self.qubes_app.domains:
-                if getattr(single_vm, 'template', None) == vm:
-                    dependent_vms += 1
-            if dependent_vms > 0:
-                QtGui.QMessageBox.warning(
-                    None, self.tr("Warning!"),
-                    self.tr("This Template Qube cannot be removed, "
-                            "because there is at least one Qube that is based "
-                            "on it.<br><small>If you want to remove this "
-                            "Template Qube and all the Qubes based on it, you "
-                            "should first remove each individual Qube that "
-                            "uses this template.</small>"))
-                return
+        dependencies = utils.vm_dependencies(self.qubes_app, vm)
+
+        if dependencies:
+            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> {} <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()
+
+            return
 
         (requested_name, ok) = QtGui.QInputDialog.getText(
             None, self.tr("Qube Removal Confirmation"),
@@ -990,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')

+ 2 - 1
qubesmanager/restore.py

@@ -134,6 +134,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
             if self.verify_only.isChecked():
                 self.backup_restore.options.verify_only = True
 
+            # pylint: disable=assignment-from-no-return
             self.vms_to_restore = self.backup_restore.get_restore_info()
 
             for vmname in self.vms_to_restore:
@@ -182,7 +183,7 @@ class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
             self.__fill_vms_list__()
 
         elif self.currentPage() is self.confirm_page:
-
+            # pylint: disable=assignment-from-no-return
             self.vms_to_restore = self.backup_restore.get_restore_info()
 
             for i in range(self.select_vms_widget.available_list.count()):

+ 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

+ 3 - 0
test-packages/qubesadmin/utils.py

@@ -8,3 +8,6 @@ def updates_vms_status(*args, **kwargs):
 
 def size_to_human(*args, **kwargs):
     return args[0]
+
+def vm_dependencies(*args):
+    return args[0]